├── changelog.md ├── docs ├── common.html ├── index.html ├── keyboard.html ├── misc.html ├── mouse.html ├── nimdoc.out.css ├── process.html ├── registry.html ├── wAuto.html └── window.html ├── license.txt ├── readme.md ├── wAuto.nim ├── wAuto ├── common.nim ├── keyboard.nim ├── misc.nim ├── mouse.nim ├── private │ └── utils.nim ├── process.nim ├── registry.nim └── window.nim └── wauto.nimble /changelog.md: -------------------------------------------------------------------------------- 1 | Version 1.3.0 2 | ------------- 3 | * Update for Nim Compiler 2.0. 4 | * misc: add clipGet and clipPut. 5 | 6 | Version 1.2.0 7 | ------------- 8 | * mouse: fix `clicks` not works in wheelUp and wheelDown. 9 | * process: add getCurrentProcess, getProcess, getCommandLine, getStats, 10 | suspend, resume, stdioClose, stdinWrite, stdoutRead, stderrRead, 11 | run, runAs, runWait, runAsWait, shellExecute, shellExecuteWait. 12 | * add misc submodule, provide isAdmin and requireAdmin. 13 | 14 | Version 1.1.0 15 | ------------- 16 | * window: add minimizeAll, minimizeAllUndo. 17 | * process: add isWow64 (#2). 18 | * registry: fix RegMultiSz writing with regWrite (#5). 19 | 20 | Version 1.0.0 21 | ------------- 22 | * Initial release 23 | -------------------------------------------------------------------------------- /docs/common.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | wAuto/common 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

wAuto/common

24 |
25 |
26 | 39 | 132 | 133 |
134 |
135 | 136 |
137 | 138 |

This module contains common definitions and procedures used in wAuto.

139 |
140 |

Types

141 |
142 |
143 |
CursorShape = enum
144 |   csUnknow, csHand, csAppStarting, csArrow, csCross, csHelp, csIBeam, csIcon,
145 |   csNo, csSize, csSizeAll, csSizeNesw, csSizeNs, csSizeNwse, csSizeWe,
146 |   csUpArrow, csWait
147 |
148 | 149 | Mouse cursor shapes. 150 | 151 |
152 |
153 |
154 |
Hotkey = tuple[modifiers: int, keyCode: int]
155 |
156 | 157 | A tuple represents a hotkey combination. modifiers is a bitwise combination of wModShift, wModCtrl, wModAlt, wModWin. keyCode is one of virtual-key codes. This tuple is compatible to hotkey in wNim/wWindow. 158 | 159 |
160 |
161 | 175 |
176 |
MouseButton = enum
177 |   mbLeft, mbRight, mbMiddle, mbPrimary, mbSecondary
178 |
179 | 180 | Mouse buttons. 181 | 182 |
183 |
184 |
185 |
Process = distinct DWORD
186 |
187 | 188 | The type of a process. 189 | 190 |
191 |
192 |
193 |
ProcessOption = enum
194 |   poStdin, poStdout, poStderr, poStderrMerged, poShow, poHide, poMaximize,
195 |   poMinimize, poCreateNewConsole, poLogonProfile, poLogonNetwork
196 |
197 | 198 | Options to create child process. 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 |
OptionsDescription
poStdinProvide a handle to the child's STDIN stream
poStdoutProvide a handle to the child's STDOUT stream
poStderrProvide a handle to the child's STDERR stream
poStderrMergedProvides the same handle for STDOUT and STDERR.
poShowShown window.
poHideHidden window.
poMaximizeMaximized window.
poMinimizeMinimized window.
poCreateNewConsoleThe child console process should be created with it's own window instead of using the parent's window.
poLogonProfileInteractive logon with profile (for RunAs).
poLogonNetworkNetwork credentials only (for RunAs).
211 | 212 |
213 |
214 |
215 |
ProcessPriority = enum
216 |   ppError, ppIdle, ppBelowNormal, ppNormal, ppAboveNormal, ppHigh, ppRealtime
217 |
218 | 219 | Priority of process. 220 | 221 |
222 |
223 |
224 |
ProcessStats = object
225 |   readOperationCount*: ULONGLONG
226 |   writeOperationCount*: ULONGLONG
227 |   otherOperationCount*: ULONGLONG
228 |   readTransferCount*: ULONGLONG
229 |   writeTransferCount*: ULONGLONG
230 |   otherTransferCount*: ULONGLONG
231 |   pageFaultCount*: DWORD
232 |   peakWorkingSetSize*: SIZE_T
233 |   workingSetSize*: SIZE_T
234 |   quotaPeakPagedPoolUsage*: SIZE_T
235 |   quotaPagedPoolUsage*: SIZE_T
236 |   quotaPeakNonPagedPoolUsage*: SIZE_T
237 |   quotaNonPagedPoolUsage*: SIZE_T
238 |   pagefileUsage*: SIZE_T
239 |   peakPagefileUsage*: SIZE_T
240 |   gdiObjects*: DWORD
241 |   userObjects*: DWORD
242 | 
243 |
244 | 245 | 246 | 247 |
248 |
249 |
250 |
RegData = object
251 |   case kind*: RegKind
252 |   of rkRegError:
253 |     nil
254 |   of rkRegDword, rkRegDwordBigEndian:
255 |     dword*: DWORD
256 |   of rkRegQword:
257 |     qword*: QWORD
258 |   else:
259 |     data*: string
260 |   
261 |
262 | 263 | The kind and data for the specified value in registry. 264 | 265 |
266 |
267 |
268 |
RegKind = enum
269 |   rkRegNone = (0, "REG_NONE"), rkRegSz = (1, "REG_SZ"),
270 |   rkRegExpandSz = (2, "REG_EXPAND_SZ"), rkRegBinary = (3, "REG_BINARY"),
271 |   rkRegDword = (4, "REG_DWORD"),
272 |   rkRegDwordBigEndian = (5, "REG_DWORD_BIG_ENDIAN"),
273 |   rkRegLink = (6, "REG_LINK"), rkRegMultiSz = (7, "REG_MULTI_SZ"),
274 |   rkRegResourceList = (8, "REG_RESOURCE_LIST"),
275 |   rkRegFullResourceDescriptor = (9, "REG_FULL_RESOURCE_DESCRIPTOR"),
276 |   rkRegResourceRequirementsList = (10, "REG_RESOURCE_REQUIREMENTS_LIST"),
277 |   rkRegQword = (11, "REG_QWORD"), rkRegError = (12, "REG_ERROR")
278 |
279 | 280 | The kinds of data type in registry. 281 | 282 |
283 |
284 |
285 |
Window = distinct HWND
286 |
287 | 288 | The type of a window. 289 | 290 |
291 |
292 | 293 |
294 |
295 |
296 |

Consts

297 |
298 |
299 |
InvalidProcess = -1'i32
300 |
301 | 302 | 303 | 304 |
305 |
306 |
307 |
InvalidWindow = 0
308 |
309 | 310 | 311 | 312 |
313 |
314 |
315 |
ppLow = ppIdle
316 |
317 | 318 | 319 | 320 |
321 |
322 | 323 |
324 |
325 |
326 |

Procs

327 |
328 |
329 |
330 |
proc opt(key: string): int {....raises: [], tags: [], forbids: [].}
331 |
332 | 333 | Gets the current setting value. 334 | 335 |
336 |
337 |
338 |
proc opt(key: string; value: int): int {.discardable, ...raises: [], tags: [],
339 |     forbids: [].}
340 |
341 | 342 | Change the global setting for window, mouse, and keyboard module. All options are case-insensitive and in milliseconds. 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 |
OptionsDescription
MouseClickDelayAlters the length of the brief pause in between mouse clicks.
MouseClickDownDelayAlters the length a click is held down before release.
MouseClickDragDelayAlters the length of the brief pause at the start and end of a mouse drag operation.
SendKeyDelayAlters the length of the brief pause in between sent keystrokes.
SendKeyDownDelayAlters the length of time a key is held down before being released during a keystroke.
WinDelayAlters how long to pause after a successful window-related operation.
WinWaitDelayAlters how long to pause during window wait operation.
351 | 352 |
353 |
354 | 355 |
356 | 357 |
358 |
359 | 360 |
361 |
362 | 363 | 368 |
369 |
370 | 371 | 372 | 373 | 374 | 375 | 376 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/keyboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | wAuto/keyboard 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

wAuto/keyboard

24 |
25 |
26 | 39 | 68 | 69 |
70 |
71 | 72 |
73 | 74 |

This module contains support to simulate keystrokes.

75 | 76 |
77 |

Procs

78 |
79 |
80 |
81 |
proc registerHotKeyEx(self: wWindow; id: int; hotkey: Hotkey): bool {.
 82 |     discardable, ...raises: [], tags: [], forbids: [].}
83 |
84 | 85 |

Registers a system wide hotkey. Every time the user presses the hotkey registered here, the window will receive a wEvent_HotKey event. If the user processes wEvent_HotKey event and set a postive result (e.g. event.result = 1), the key will not be blocked.

86 |

The difference from wNim/wWindow.registerHotKey() is that this procedure use low-level keyboard hook instead of RegisterHotKey API. So that the system default key combination can be replaced, For example: Win + R.

87 | 88 | 89 |
90 |
91 |
92 |
proc registerHotKeyEx(self: wWindow; id: int; hotkey: string): bool {.inline,
 93 |     discardable, ...raises: [], tags: [], forbids: [].}
94 |
95 | 96 | Registers a system wide hotkey. Accept a hotkey string. 97 |

Example:

98 |
import wNim/wFrame
 99 | 
100 | proc example() =
101 |   var frame = Frame()
102 |   frame.registerHotKeyEx(0, "Ctrl + Alt + F1")
103 | 104 |
105 |
106 | 107 |
108 |
109 |
110 |
proc send(text: string; raw = false; window = InvalidWindow; attach = false;
111 |           restoreCapslock = false) {....raises: [Exception], tags: [RootEffect],
112 |                                      forbids: [].}
113 |
114 | 115 | Sends simulated keystrokes to the active window. If raw is true, keys are sent raw. If window is specified, it attempts to keep it active during send(). If attach is true, it attaches input threads when during send(). If restoreCapslock is true, the state of capslock is restored after send(). 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 |
SyntaxDescription
+Combine next key with SHIFT.
!Combine next key with ALT.
^Combine next key with CTRL.
#Combine next key with Windows key.
{!}!
{#}#
{+}+
{^}^
{{}{
{}}}
{SPACE}SPACE
{ENTER}ENTER
{ALT}ALT
{BACKSPACE} or {BS}BACKSPACE
{DELETE} or {DEL}DELETE
{UP}Up arrow
{DOWN}Down arrow
{LEFT}Left arrow
{RIGHT}Right arrow
{HOME}HOME
{END}END
{ESCAPE} or {ESC}ESCAPE
{INSERT} or {INS}INS
{PGUP}PageUp
{PGDN}PageDown
{F1} - {F12}Function keys
{TAB}TAB
{PRINTSCREEN}Print Screen key
{LWIN}Left Windows key
{RWIN}Right Windows key
{NUMLOCK on/off/toggle}NUMLOCK (on/off/toggle)
{CAPSLOCK on/off/toggle}CAPSLOCK (on/off/toggle)
{SCROLLLOCK on/off/toggle}SCROLLLOCK (on/off/toggle)
{BREAK}Break
{PAUSE}Pause
{NUMPAD0} - {NUMPAD9}Numpad digits
{NUMPADMULT}Numpad Multiply
{NUMPADADD}Numpad Add
{NUMPADSUB}Numpad Subtract
{NUMPADDIV}Numpad Divide
{NUMPADDOT}Numpad period
{NUMPADENTER}Enter key on the numpad
{APPSKEY}Windows App key
{LALT}Left ALT key
{RALT}Right ALT key
{LCTRL}Left CTRL key
{RCTRL}Right CTRL key
{LSHIFT}Left Shift key
{RSHIFT}Right Shift key
{SLEEP}Computer SLEEP key
{ALTDOWN}Holds the ALT key down until {ALTUP}
{LALTDOWN} or {RALTDOWN}Holds the left or right ALT key down until {LALTUP} or {RALTUP}
{SHIFTDOWN}Holds the SHIFT key down until {SHIFTUP}
{LSHIFTDOWN} or {RSHIFTDOWN}Holds the left or right SHIFT key down until {LALTUP} or {RALTUP}
{CTRLDOWN}Holds the CTRL key down until {CTRLUP}
{LCTRLDOWN} or {RCTRLDOWN}Holds the left or right CTRL key down until {LCTRLUP} or {RCTRLUP}
{WINDOWN}Holds the left Windows key down until {WINUP}
{LWINDOWN} or {RWINDOWN}Holds the left or right Windows key down until {LWINUP} or {RWINUP}
{ASC nnnn}Send the specified ASCII character
{BROWSER_BACK}Select the browser "back" button
{BROWSER_FORWARD}Select the browser "forward" button
{BROWSER_REFRESH}Select the browser "refresh" button
{BROWSER_STOP}Select the browser "stop" button
{BROWSER_SEARCH}Select the browser "search" button
{BROWSER_FAVORITES}Select the browser "favorites" button
{BROWSER_HOME}Launch the browser and go to the home page
{VOLUME_MUTE}Mute the volume
{VOLUME_DOWN}Reduce the volume
{VOLUME_UP}Increase the volume
{MEDIA_NEXT}Select next track in media player
{MEDIA_PREV}Select previous track in media player
{MEDIA_STOP}Stop media player
{MEDIA_PLAY_PAUSE}Play/pause media player
{LAUNCH_MAIL}Launch the email application
{LAUNCH_MEDIA}Launch media player
{LAUNCH_APP1}Launch user app1
{LAUNCH_APP2}Launch user app2
{KEY n}KEY (or character) will be sent repeated n times
195 |

Example:

196 |
import window
197 | 
198 | proc example() =
199 |   send("#r")
200 |   send("notepad{enter}")
201 |   send("abc{BS 3}def", window=waitAny(window.className == "Notepad"))
202 |   send("!fxn")
203 | 204 |
205 |
206 |
207 |
proc send(window: Window; text: string) {....raises: [], tags: [], forbids: [].}
208 |
209 | 210 | Sends a string of characters to a window. This window must process WM_CHAR event, for example: an editor contorl. 211 | 212 |
213 |
214 | 215 |
216 |
217 |
218 |
proc unregisterHotKeyEx(self: wWindow; id: int): bool {.discardable, ...raises: [],
219 |     tags: [], forbids: [].}
220 |
221 | 222 | Unregisters a system wide hotkey. 223 | 224 |
225 |
226 | 227 |
228 | 229 |
230 |
231 | 232 | 233 |
234 |
235 | 236 | 241 |
242 |
243 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /docs/misc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | wAuto/misc 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

wAuto/misc

24 |
25 |
26 | 39 | 66 | 67 |
68 |
69 | 70 |
71 | 72 |

This module contains misc. functions for wAuto.

73 |
74 |

Procs

75 |
76 |
77 |
78 |
proc clipGet(allowFiles = false): string {....raises: [Exception],
 79 |     tags: [RootEffect], forbids: [].}
80 |
81 | 82 | Retrieves text from the clipboard. When allowFiles is true and multiple selecting file/dir are stored in the clipboard, the filename/dirname are returned as texts separated by @LF. 83 | 84 |
85 |
86 | 87 |
88 |
89 |
90 |
proc clipPut(text: string) {....raises: [wDataObjectError, Exception],
 91 |                              tags: [RootEffect], forbids: [].}
92 |
93 | 94 | Writes text to the clipboard. An empty string "" will empty the clipboard. 95 | 96 |
97 |
98 | 99 |
100 |
101 |
102 |
proc isAdmin(): bool {....raises: [], tags: [], forbids: [].}
103 |
104 | 105 | Checks if the current user has full administrator privileges. 106 | 107 |
108 |
109 | 110 |
111 |
112 |
113 |
proc requireAdmin(raiseError = true) {....raises: [OSError], tags: [ReadIOEffect],
114 |                                        forbids: [].}
115 |
116 | 117 | Elevate the current process during runtime by restarting it. Raise an error if the user cancel it if raiseError is true. 118 | 119 |
120 |
121 | 122 |
123 | 124 |
125 |
126 | 127 |
128 |
129 | 130 | 135 |
136 |
137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /docs/mouse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | wAuto/mouse 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

wAuto/mouse

24 |
25 | 98 |
99 | 100 |
101 | 102 |

This module contains support to simulate mouse movements and clicks. For all functions that receives pos (or x, y) as parameter, wDefaultPoint or wDefault can be specified to indicate not to change.

103 |

The speed parameter is the speed to move the mouse in the range 1 (fastest) to 100 (slowest). A speed of 0 will move the mouse instantly.

104 |

105 | 106 |
107 |

Procs

108 |
109 |
110 |
111 |
proc click(button: MouseButton = mbLeft; pos = wDefaultPoint; clicks = 1;
112 |            speed: range[0 .. 100] = 10) {....raises: [], tags: [], forbids: [].}
113 |
114 | 115 | Perform a mouse click operation at the position pos. clicks is the number of times to click the mouse. 116 | 117 |
118 |
119 |
120 |
proc click(button: MouseButton; x = wDefault; y = wDefault; clicks = 1;
121 |            speed: range[0 .. 100] = 10) {....raises: [], tags: [], forbids: [].}
122 |
123 | 124 | Perform a mouse click operation at the position (x, y). clicks is the number of times to click the mouse. 125 | 126 |
127 |
128 | 129 |
130 |
131 |
132 |
proc clickDrag(button: MouseButton = mbLeft; pos1 = wDefaultPoint;
133 |                pos2 = wDefaultPoint; speed: range[0 .. 100] = 10) {....raises: [],
134 |     tags: [], forbids: [].}
135 |
136 | 137 | Perform a mouse click and drag operation from pos1 to pos2. 138 | 139 |
140 |
141 | 142 |
143 |
144 |
145 |
proc down(button: MouseButton = mbLeft) {....raises: [], tags: [], forbids: [].}
146 |
147 | 148 | Perform a mouse down event at the current mouse position. 149 | 150 |
151 |
152 | 153 |
154 |
155 |
156 |
proc getCursorPosition(): wPoint {....raises: [], tags: [], forbids: [].}
157 |
158 | 159 | Retrieves the current position of the mouse cursor. 160 | 161 |
162 |
163 | 164 |
165 |
166 |
167 |
proc getCursorShape(): CursorShape {....raises: [], tags: [], forbids: [].}
168 |
169 | 170 | Returns the current mouse cursor shape. 171 | 172 |
173 |
174 | 175 |
176 |
177 |
178 |
proc move(pos = wDefaultPoint; speed: range[0 .. 100] = 10) {....raises: [],
179 |     tags: [], forbids: [].}
180 |
181 | 182 | Moves the mouse pointer to pos. 183 | 184 |
185 |
186 |
187 |
proc move(x = wDefault; y = wDefault; speed: range[0 .. 100] = 10) {....raises: [],
188 |     tags: [], forbids: [].}
189 |
190 | 191 | Moves the mouse pointer to (x, y). 192 | 193 |
194 |
195 | 196 |
197 |
198 |
199 |
proc up(button: MouseButton = mbLeft) {....raises: [], tags: [], forbids: [].}
200 |
201 | 202 | Perform a mouse up event at the current mouse position. 203 | 204 |
205 |
206 | 207 |
208 |
209 |
210 |
proc wheelDown(clicks = 1) {....raises: [], tags: [], forbids: [].}
211 |
212 | 213 | Moves the mouse wheel down. clicks is the number of times to move the wheel. 214 | 215 |
216 |
217 | 218 |
219 |
220 |
221 |
proc wheelUp(clicks = 1) {....raises: [], tags: [], forbids: [].}
222 |
223 | 224 | Moves the mouse wheel up. clicks is the number of times to move the wheel. 225 | 226 |
227 |
228 | 229 |
230 | 231 |
232 |
233 | 234 | 235 |
236 |
237 | 238 | 243 |
244 |
245 | 246 | 247 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /docs/registry.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | wAuto/registry 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

wAuto/registry

24 |
25 |
26 | 39 | 88 | 89 |
90 |
91 | 92 |
93 | 94 |

This module contains support to manipulate Windows registry.

95 |

A registry key must start with "HKEY_LOCAL_MACHINE" ("HKLM") or "HKEY_USERS" ("HKU") or "HKEY_CURRENT_USER" ("HKCU") or "HKEY_CLASSES_ROOT" ("HKCR") or "HKEY_CURRENT_CONFIG" ("HKCC").

96 |

When running on 64-bit Windows if you want to read a value specific to the 64-bit environment you have to suffix the HK... with 64 i.e. HKLM64.

97 |

To access the (Default) value use "" (an empty string) for the value name.

98 |

When reading a REG_MULTI_SZ key the multiple entries are separated by '\0' - use with .split('\0') to get a seq of each entry.

99 |

It is possible to access remote registries by using a keyname in the form r"\\computername\keyname". To use this feature you must have the correct access rights.

100 |

101 | 102 |
103 |

Procs

104 |
105 |
106 |
107 |
proc `==`(a, b: RegData): bool {....raises: [], tags: [], forbids: [].}
108 |
109 | 110 | Checks for equality between two RegData variables. 111 | 112 |
113 |
114 | 115 |
116 |
117 |
118 |
proc regDelete(key: string): bool {.discardable, ...raises: [], tags: [],
119 |                                     forbids: [].}
120 |
121 | 122 | Deletes the entire key from the registry. Deleting from the registry is potentially dangerous--please exercise caution! 123 |

Example:

124 |
proc example() =
125 |   regDelete(r"HKEY_CURRENT_USER\Software\wAuto")
126 | 127 |
128 |
129 |
130 |
proc regDelete(key: string; name: string): bool {.discardable, ...raises: [],
131 |     tags: [], forbids: [].}
132 |
133 | 134 | Deletes a value from the registry. 135 |

Example:

136 |
proc example() =
137 |   regDelete(r"HKEY_CURRENT_USER\Software\wAuto", "Key3")
138 | 139 |
140 |
141 | 142 |
143 |
144 |
145 |
proc regRead(key: string; value: string): RegData {....raises: [], tags: [],
146 |     forbids: [].}
147 |
148 | 149 | Reads a value from the registry. 150 |

Example:

151 |
proc example() =
152 |   echo regRead(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion", "ProgramFilesDir")
153 | 154 |
155 |
156 | 157 |
158 |
159 |
160 |
proc regWrite(key: string): bool {.discardable, ...raises: [], tags: [],
161 |                                    forbids: [].}
162 |
163 | 164 | Creates a key in the registry. 165 |

Example:

166 |
proc example() =
167 |   regWrite(r"HKEY_CURRENT_USER\Software\wAuto")
168 | 169 |
170 |
171 |
172 |
proc regWrite(key: string; name: string; value: DWORD): bool {.discardable,
173 |     ...raises: [], tags: [], forbids: [].}
174 |
175 | 176 | Creates a value of REG_DWORD type in the registry. 177 |

Example:

178 |
proc example() =
179 |   regWrite(r"HKEY_CURRENT_USER\Software\wAuto", "Key3", 12345)
180 | 181 |
182 |
183 |
184 |
proc regWrite(key: string; name: string; value: RegData): bool {.discardable,
185 |     ...raises: [], tags: [], forbids: [].}
186 |
187 | 188 | Creates a value in the registry. 189 |

Example:

190 |
proc example() =
191 |   regWrite(r"HKEY_CURRENT_USER\Software\wAuto", "Key1", RegData(kind: rkRegSz, data: "Test"))
192 | 193 |
194 |
195 |
196 |
proc regWrite(key: string; name: string; value: string): bool {.discardable,
197 |     ...raises: [], tags: [], forbids: [].}
198 |
199 | 200 | Creates a value of REG_SZ type in the registry. 201 |

Example:

202 |
proc example() =
203 |   regWrite(r"HKEY_CURRENT_USER\Software\wAuto", "Key2", "Test")
204 | 205 |
206 |
207 | 208 |
209 | 210 |
211 |
212 |
213 |

Iterators

214 |
215 |
216 |
217 |
iterator regKeys(key: string): string {....raises: [], tags: [], forbids: [].}
218 |
219 | 220 | Iterates over subkeys. 221 | 222 |
223 |
224 | 225 |
226 |
227 |
228 |
iterator regValues(key: string): tuple[name: string, kind: RegKind] {.
229 |     ...raises: [], tags: [], forbids: [].}
230 |
231 | 232 | Iterates over name and kind of values. 233 | 234 |
235 |
236 | 237 |
238 | 239 |
240 |
241 | 242 | 243 |
244 |
245 | 246 | 251 |
252 |
253 | 254 | 255 | 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /docs/wAuto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | wAuto 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

wAuto

24 |
25 |
26 | 39 |
    40 | 41 | 42 | 43 |
44 | 45 |
46 |
47 | 48 |
49 | 50 |

wAuto is the Windows automation module for nim based on winim and wNim. It contains support to simulate keystrokes and mouse movements, manipulate windows, processes, and registry. Some functions are inspired by AutoIt Script.

51 |

The getters and setters in wAuto, just like in wNim, can be simplized. For example:

52 |
assert getPosition(getActiveWindow()) == activeWindow().position

wAuto contains following submodules.

53 | 61 |

Modules can be imoprted all in one, or be imported one by one. For example:

62 |
import wAuto # import all
63 | import wAuto/window # import window module only

64 | 65 | 66 | 67 |
68 |
69 | 70 | 75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Chen Kai-Hung, Ward 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # wAuto 2 | 3 | wAuto is the Windows automation module for nim based on 4 | [winim](https://github.com/khchen/winim) and 5 | [wNim](https://github.com/khchen/wNim). It contains support to simulate 6 | keystrokes and mouse movements, manipulate windows, processes, and registry. 7 | Some functions are inspired by [AutoIt Script](https://www.autoitscript.com) 8 | 9 | ## Install 10 | With git on windows: 11 | 12 | nimble install wAuto 13 | 14 | Without git: 15 | 16 | 1. Download and unzip this moudle (by click "Clone or download" button). 17 | 2. Start a console, change current dir to the folder which include "wAuto.nimble" file. 18 | (for example: C:\wAuto-master\wAuto-master>) 19 | 3. Run "nimble install" 20 | 21 | ## Example 22 | 23 | ```nim 24 | import wAuto 25 | 26 | # Open "Run" box 27 | send("#r") 28 | 29 | # Start notepad.exe 30 | send("notepad{enter}") 31 | 32 | # Wait the window 33 | let notepad = waitAny(window.className == "Notepad" and window.isActive) 34 | 35 | # Send some words 36 | send("Hello, world") 37 | 38 | # Drag the mouse cursor to select 39 | clickDrag(pos1=notepad.clientPosition(0, 0), pos2=notepad.clientPosition(200, 0)) 40 | 41 | # Copy it 42 | send("^c") 43 | 44 | # Paste 10 times slowly 45 | opt("SendKeyDelay", 250) 46 | send("^{v 10}") 47 | 48 | # Terminates the process 49 | kill(notepad.process) 50 | ``` 51 | 52 | ## Docs 53 | * https://khchen.github.io/wAuto 54 | 55 | ## License 56 | Read license.txt for more details. 57 | 58 | Copyright (c) Chen Kai-Hung, Ward. All rights reserved. 59 | 60 | ## Donate 61 | If this project help you reduce time to develop, you can give me a cup of coffee :) 62 | 63 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/khchen0915?country.x=TW&locale.x=zh_TW) 64 | -------------------------------------------------------------------------------- /wAuto.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | ## wAuto is the Windows automation module for nim based on 9 | ## `winim `_ and 10 | ## `wNim `_. 11 | ## It contains support to simulate keystrokes and mouse movements, manipulate windows, 12 | ## processes, and registry. Some functions are inspired by 13 | ## `AutoIt Script `_. 14 | ## 15 | ## The getters and setters in wAuto, just like in wNim, can be simplized. 16 | ## For example: 17 | ## 18 | ## .. code-block:: Nim 19 | ## assert getPosition(getActiveWindow()) == activeWindow().position 20 | ## 21 | ## wAuto contains following submodules. 22 | ## 23 | ## - `common `_ 24 | ## - `window `_ 25 | ## - `mouse `_ 26 | ## - `keyboard `_ 27 | ## - `process `_ 28 | ## - `registry `_ 29 | ## - `misc `_ 30 | ## 31 | ## Modules can be imoprted all in one, or be imported one by one. 32 | ## For example: 33 | ## 34 | ## .. code-block:: Nim 35 | ## import wAuto # import all 36 | ## import wAuto/window # import window module only 37 | 38 | import wAuto/[common, window, mouse, keyboard, process, registry, misc] 39 | export common, window, mouse, keyboard, process, registry, misc 40 | -------------------------------------------------------------------------------- /wAuto/common.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | ## This module contains common definitions and procedures used in wAuto. 9 | 10 | import tables, strutils 11 | import winim/lean 12 | 13 | type 14 | Window* = distinct HWND 15 | ## The type of a window. 16 | 17 | Process* = distinct DWORD 18 | ## The type of a process. 19 | 20 | MouseButton* = enum 21 | ## Mouse buttons. 22 | mbLeft, mbRight, mbMiddle, mbPrimary, mbSecondary 23 | 24 | CursorShape* = enum 25 | ## Mouse cursor shapes. 26 | csUnknow, csHand, csAppStarting, csArrow, csCross, csHelp, csIBeam, csIcon, 27 | csNo, csSize, csSizeAll, csSizeNesw, csSizeNs, csSizeNwse, csSizeWe, csUpArrow, 28 | csWait 29 | 30 | ProcessPriority* = enum 31 | ## Priority of process. 32 | ppError, ppIdle, ppBelowNormal, ppNormal, ppAboveNormal, ppHigh, ppRealtime 33 | 34 | ProcessOption* = enum 35 | ## Options to create child process. 36 | ## 37 | ## ================================ ============================================================= 38 | ## Options Description 39 | ## ================================ ============================================================= 40 | ## poStdin Provide a handle to the child's STDIN stream 41 | ## poStdout Provide a handle to the child's STDOUT stream 42 | ## poStderr Provide a handle to the child's STDERR stream 43 | ## poStderrMerged Provides the same handle for STDOUT and STDERR. 44 | ## poShow Shown window. 45 | ## poHide Hidden window. 46 | ## poMaximize Maximized window. 47 | ## poMinimize Minimized window. 48 | ## poCreateNewConsole The child console process should be created with it's own window instead of using the parent's window. 49 | ## poLogonProfile Interactive logon with profile (for RunAs). 50 | ## poLogonNetwork Network credentials only (for RunAs). 51 | ## ================================ ============================================================= 52 | poStdin, poStdout, poStderr, poStderrMerged 53 | poShow, poHide, poMaximize, poMinimize 54 | poCreateNewConsole, poLogonProfile, poLogonNetwork 55 | 56 | Hotkey* = tuple[modifiers: int, keyCode: int] 57 | ## A tuple represents a hotkey combination. *modifiers* is a bitwise combination 58 | ## of wModShift, wModCtrl, wModAlt, wModWin. *keyCode* is one of 59 | ## `virtual-key codes `_. 60 | ## This tuple is compatible to hotkey in 61 | ## `wNim/wWindow `_. 62 | 63 | MenuItem* = object 64 | ## Represents a menu item. 65 | handle*: HMENU 66 | index*: int 67 | text*: string 68 | id*: int 69 | byPos*: bool 70 | 71 | RegKind* = enum 72 | ## The kinds of data type in registry. 73 | rkRegNone = (0, "REG_NONE") 74 | rkRegSz = (1, "REG_SZ") 75 | rkRegExpandSz = (2, "REG_EXPAND_SZ") 76 | rkRegBinary = (3, "REG_BINARY") 77 | rkRegDword = (4, "REG_DWORD") 78 | rkRegDwordBigEndian = (5, "REG_DWORD_BIG_ENDIAN") 79 | rkRegLink = (6, "REG_LINK") 80 | rkRegMultiSz = (7, "REG_MULTI_SZ") 81 | rkRegResourceList = (8, "REG_RESOURCE_LIST") 82 | rkRegFullResourceDescriptor = (9, "REG_FULL_RESOURCE_DESCRIPTOR") 83 | rkRegResourceRequirementsList = (10, "REG_RESOURCE_REQUIREMENTS_LIST") 84 | rkRegQword = (11, "REG_QWORD") 85 | rkRegError = (12, "REG_ERROR") 86 | 87 | RegData* = object 88 | ## The kind and data for the specified value in registry. 89 | case kind*: RegKind 90 | of rkRegError: nil 91 | of rkRegDword, rkRegDwordBigEndian: dword*: DWORD 92 | of rkRegQword: qword*: QWORD 93 | else: data*: string 94 | 95 | ProcessStats* = object 96 | readOperationCount*: ULONGLONG 97 | writeOperationCount*: ULONGLONG 98 | otherOperationCount*: ULONGLONG 99 | readTransferCount*: ULONGLONG 100 | writeTransferCount*: ULONGLONG 101 | otherTransferCount*: ULONGLONG 102 | pageFaultCount*: DWORD 103 | peakWorkingSetSize*: SIZE_T 104 | workingSetSize*: SIZE_T 105 | quotaPeakPagedPoolUsage*: SIZE_T 106 | quotaPagedPoolUsage*: SIZE_T 107 | quotaPeakNonPagedPoolUsage*: SIZE_T 108 | quotaNonPagedPoolUsage*: SIZE_T 109 | pagefileUsage*: SIZE_T 110 | peakPagefileUsage*: SIZE_T 111 | gdiObjects*: DWORD 112 | userObjects*: DWORD 113 | 114 | const 115 | InvalidProcess* = Process -1 116 | 117 | InvalidWindow* = Window 0 118 | 119 | ppLow* = ppIdle 120 | 121 | 122 | var table {.threadvar.}: Table[string, int] 123 | 124 | proc opt*(key: string): int = 125 | ## Gets the current setting value. 126 | let key = key.toLowerAscii 127 | table.withValue(key, value) do: 128 | result = value[] 129 | 130 | do: 131 | result = case key 132 | of "winwaitdelay": 250 133 | of "windelay": 10 134 | of "mouseclickdelay": 10 135 | of "mouseclickdowndelay": 10 136 | of "mouseclickdragdelay": 250 137 | of "sendkeydowndelay": 5 138 | of "sendkeydelay": 5 139 | else: 0 140 | 141 | proc opt*(key: string, value: int): int {.discardable.} = 142 | ## Change the global setting for window, mouse, and keyboard module. 143 | ## All options are case-insensitive and in milliseconds. 144 | ## 145 | ## ================================ ============================================================= 146 | ## Options Description 147 | ## ================================ ============================================================= 148 | ## MouseClickDelay Alters the length of the brief pause in between mouse clicks. 149 | ## MouseClickDownDelay Alters the length a click is held down before release. 150 | ## MouseClickDragDelay Alters the length of the brief pause at the start and end of a mouse drag operation. 151 | ## SendKeyDelay Alters the length of the brief pause in between sent keystrokes. 152 | ## SendKeyDownDelay Alters the length of time a key is held down before being released during a keystroke. 153 | ## WinDelay Alters how long to pause after a successful window-related operation. 154 | ## WinWaitDelay Alters how long to pause during window wait operation. 155 | ## ================================ ============================================================= 156 | let key = key.toLowerAscii 157 | 158 | table.withValue(key, value) do: 159 | result = value[] 160 | do: 161 | result = opt(key) 162 | 163 | table[key] = value 164 | -------------------------------------------------------------------------------- /wAuto/keyboard.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | ## This module contains support to simulate keystrokes. 9 | 10 | import strutils, tables 11 | import winim/lean 12 | import wNim/[wApp, wMacros, wWindow, wHotkeyCtrl] 13 | import npeg, npeg/lib/utf8 14 | import common, window, private/utils 15 | 16 | export common, wApp # for wKeyCodes 17 | 18 | type 19 | KeyModifier = enum 20 | kmLShift, kmRShift, kmLControl, kmRControl, kmLAlt, kmRAlt, kmLWin, kmRWin 21 | 22 | KeyDownUp = enum 23 | kDown, kUp 24 | 25 | KeyToggle = enum 26 | ktNil, ktOn, ktOff, ktToggle, ktDown, ktUp 27 | 28 | KeyCommand = object 29 | text: string 30 | count: int 31 | toggle: KeyToggle 32 | modifiers: set[KeyModifier] 33 | 34 | KeyItem = object 35 | vk: int 36 | shift: bool 37 | extend: bool 38 | 39 | ModifierItem = object 40 | modifier: KeyModifier 41 | downup: KeyDownUp 42 | 43 | WinData = tuple[hwnd: HWND, id: int] 44 | 45 | HotkeyData = object 46 | hHook: HHOOK 47 | table: Table[Hotkey, WinData] 48 | lastKeyCode: int 49 | lastModifiers: int 50 | 51 | proc sendKeyboardEvent(vk: int, keyDownUp: KeyDownUp, isExtended: bool = false) = 52 | var input = INPUT(`type`: INPUT_KEYBOARD) 53 | input.ki.wVk = WORD vk 54 | input.ki.wScan = WORD MapVirtualKey(UINT vk, MAPVK_VK_TO_VSC) 55 | if keyDownUp == kUp: input.ki.dwFlags = input.ki.dwFlags or KEYEVENTF_KEYUP 56 | if isExtended: input.ki.dwFlags = input.ki.dwFlags or KEYEVENTF_EXTENDEDKEY 57 | SendInput(1, &input, cint sizeof(INPUT)) 58 | 59 | proc sendUnicodeKeyboardEvent(unicode: WCHAR, keyDownUp: KeyDownUp, isExtended: bool = false) = 60 | var input = INPUT(`type`: INPUT_KEYBOARD) 61 | input.ki.wScan = unicode 62 | input.ki.dwFlags = KEYEVENTF_UNICODE 63 | if keyDownUp == kUp: input.ki.dwFlags = input.ki.dwFlags or KEYEVENTF_KEYUP 64 | if isExtended: input.ki.dwFlags = input.ki.dwFlags or KEYEVENTF_EXTENDEDKEY 65 | SendInput(1, addr input, cint sizeof(INPUT)) 66 | 67 | proc sendModifier(modifiers: set[KeyModifier], keyDownUp: KeyDownUp, window = InvalidWindow) = 68 | template sendModifierKeyboardEvent(vk: int, isExtended: bool) = 69 | if window != InvalidWindow: window.activate() 70 | sendKeyboardEvent(vk, keyDownUp, isExtended) 71 | sleep(opt("sendkeydelay")) 72 | 73 | if kmLShift in modifiers: sendModifierKeyboardEvent(VK_LSHIFT, false) 74 | if kmRShift in modifiers: sendModifierKeyboardEvent(VK_RSHIFT, true) 75 | if kmLControl in modifiers: sendModifierKeyboardEvent(VK_LCONTROL, false) 76 | if kmRControl in modifiers: sendModifierKeyboardEvent(VK_RCONTROL, true) 77 | if kmLAlt in modifiers: sendModifierKeyboardEvent(VK_LMENU, false) 78 | if kmRAlt in modifiers: sendModifierKeyboardEvent(VK_RMENU, true) 79 | if kmLWin in modifiers: sendModifierKeyboardEvent(VK_LWIN, true) 80 | if kmRWin in modifiers: sendModifierKeyboardEvent(VK_RWIN, true) 81 | 82 | proc pressKey(vk: int, downup: KeyDownUp, isExtended: bool, window = InvalidWindow) = 83 | if window != InvalidWindow: window.activate() 84 | sendKeyboardEvent(vk, downup) 85 | case downup 86 | of kDown: sleep(opt("sendkeydowndelay")) 87 | of kUp: sleep(opt("sendkeydelay")) 88 | 89 | proc sendKey(vk: int, modifiers: set[KeyModifier], count: int, isExtended: bool, window = InvalidWindow) = 90 | if modifiers.card != 0: 91 | sendModifier(modifiers, kDown, window) 92 | sleep(opt("sendkeydowndelay")) 93 | 94 | for i in 1..count: 95 | pressKey(vk, kDown, isExtended, window) 96 | pressKey(vk, kUp, isExtended, window) 97 | 98 | if modifiers.card != 0: 99 | sendModifier(modifiers, kUp, window) 100 | sleep(opt("sendkeydelay")) 101 | 102 | proc toggleKey(vk: int, toggle: KeyToggle, window = InvalidWindow): KeyToggle {.discardable.} = 103 | result = if (GetKeyState(cint vk) and 1) != 0: ktOn else: ktOff 104 | if toggle in {ktNil, result}: return # nothing to do 105 | 106 | if window != InvalidWindow: window.activate() 107 | sendKeyboardEvent(vk, kDown) 108 | sleep(opt("sendkeydowndelay")) 109 | 110 | if window != InvalidWindow: window.activate() 111 | sendKeyboardEvent(vk, kUp) 112 | sleep(opt("sendkeydelay")) 113 | 114 | proc sendStringByMessage(wstr: wstring, window: Window) = 115 | for unicode in wstr: 116 | PostMessage(HWND window, WM_CHAR, WPARAM unicode, 0) 117 | sleep(opt("sendkeydelay")) 118 | 119 | proc sendString(wstr: wstring, count: int = 1, window = InvalidWindow) = 120 | for i in 1..count: 121 | for unicode in wstr: 122 | if window != InvalidWindow: window.activate() 123 | sendUnicodeKeyboardEvent(unicode, kDown) 124 | sleep(opt("sendkeydowndelay")) 125 | 126 | if window != InvalidWindow: window.activate() 127 | sendUnicodeKeyboardEvent(unicode, kUp) 128 | sleep(opt("sendkeydelay")) 129 | 130 | proc initKeyTable(): Table[string, KeyItem] = 131 | for i in 'a'..'z': result[$i] = KeyItem(vk: int i.toUpperAscii) 132 | for i in 'A'..'Z': result[$i] = KeyItem(vk: int i, shift: true) 133 | for i in '0'..'9': result[$i] = KeyItem(vk: int i) 134 | 135 | result[";"] = KeyItem(vk: VK_OEM_1) 136 | result[":"] = KeyItem(vk: VK_OEM_1, shift: true) 137 | result["/"] = KeyItem(vk: VK_OEM_2) 138 | result["?"] = KeyItem(vk: VK_OEM_2, shift: true) 139 | result["`"] = KeyItem(vk: VK_OEM_3) 140 | result["~"] = KeyItem(vk: VK_OEM_3, shift: true) 141 | result["["] = KeyItem(vk: VK_OEM_4) 142 | result["{"] = KeyItem(vk: VK_OEM_4, shift: true) 143 | result["\\"] = KeyItem(vk: VK_OEM_5) 144 | result["|"] = KeyItem(vk: VK_OEM_5, shift: true) 145 | result["]"] = KeyItem(vk: VK_OEM_6) 146 | result["}"] = KeyItem(vk: VK_OEM_6, shift: true) 147 | result["'"] = KeyItem(vk: VK_OEM_7) 148 | result["\""] = KeyItem(vk: VK_OEM_7, shift: true) 149 | result["="] = KeyItem(vk: VK_OEM_PLUS) 150 | result["+"] = KeyItem(vk: VK_OEM_PLUS, shift: true) 151 | result["-"] = KeyItem(vk: VK_OEM_MINUS) 152 | result["_"] = KeyItem(vk: VK_OEM_MINUS, shift: true) 153 | result[","] = KeyItem(vk: VK_OEM_COMMA) 154 | result["<"] = KeyItem(vk: VK_OEM_COMMA, shift: true) 155 | result["."] = KeyItem(vk: VK_OEM_PERIOD) 156 | result[">"] = KeyItem(vk: VK_OEM_PERIOD, shift: true) 157 | result[")"] = KeyItem(vk: int '0', shift: true) 158 | result["!"] = KeyItem(vk: int '1', shift: true) 159 | result["@"] = KeyItem(vk: int '2', shift: true) 160 | result["#"] = KeyItem(vk: int '3', shift: true) 161 | result["$"] = KeyItem(vk: int '4', shift: true) 162 | result["%"] = KeyItem(vk: int '5', shift: true) 163 | result["^"] = KeyItem(vk: int '6', shift: true) 164 | result["&"] = KeyItem(vk: int '7', shift: true) 165 | result["*"] = KeyItem(vk: int '8', shift: true) 166 | result["("] = KeyItem(vk: int '9', shift: true) 167 | result["SPACE"] = KeyItem(vk: VK_SPACE) 168 | result["ENTER"] = KeyItem(vk: VK_RETURN) 169 | result["ALT"] = KeyItem(vk: VK_MENU) 170 | result["BACKSPACE"] = KeyItem(vk: VK_BACK) 171 | result["BS"] = KeyItem(vk: VK_BACK) 172 | result["DELETE"] = KeyItem(vk: VK_DELETE, extend: true) 173 | result["DEL"] = KeyItem(vk: VK_DELETE, extend: true) 174 | result["UP"] = KeyItem(vk: VK_UP, extend: true) 175 | result["DOWN"] = KeyItem(vk: VK_DOWN, extend: true) 176 | result["LEFT"] = KeyItem(vk: VK_LEFT, extend: true) 177 | result["RIGHT"] = KeyItem(vk: VK_RIGHT, extend: true) 178 | result["HOME"] = KeyItem(vk: VK_HOME, extend: true) 179 | result["END"] = KeyItem(vk: VK_END, extend: true) 180 | result["ESCAPE"] = KeyItem(vk: VK_ESCAPE) 181 | result["ESC"] = KeyItem(vk: VK_ESCAPE) 182 | result["INSERT"] = KeyItem(vk: VK_INSERT, extend: true) 183 | result["INS"] = KeyItem(vk: VK_INSERT, extend: true) 184 | result["PGUP"] = KeyItem(vk: VK_PRIOR, extend: true) 185 | result["PAGEUP"] = KeyItem(vk: VK_PRIOR, extend: true) 186 | result["PGDN"] = KeyItem(vk: VK_NEXT, extend: true) 187 | result["PAGEDOWN"] = KeyItem(vk: VK_NEXT, extend: true) 188 | result["F1"] = KeyItem(vk: VK_F1) 189 | result["F2"] = KeyItem(vk: VK_F2) 190 | result["F3"] = KeyItem(vk: VK_F3) 191 | result["F4"] = KeyItem(vk: VK_F4) 192 | result["F5"] = KeyItem(vk: VK_F5) 193 | result["F6"] = KeyItem(vk: VK_F6) 194 | result["F7"] = KeyItem(vk: VK_F7) 195 | result["F8"] = KeyItem(vk: VK_F8) 196 | result["F9"] = KeyItem(vk: VK_F9) 197 | result["F10"] = KeyItem(vk: VK_F10) 198 | result["F11"] = KeyItem(vk: VK_F11) 199 | result["F12"] = KeyItem(vk: VK_F12) 200 | result["TAB"] = KeyItem(vk: VK_TAB) 201 | result["PRINTSCREEN"] = KeyItem(vk: VK_SNAPSHOT) 202 | result["LWIN"] = KeyItem(vk: VK_LMENU, extend: true) 203 | result["RWIN"] = KeyItem(vk: VK_RMENU, extend: true) 204 | result["NUMLOCK"] = KeyItem(vk: VK_NUMLOCK) 205 | result["CAPSLOCK"] = KeyItem(vk: VK_CAPITAL) 206 | result["SCROLLLOCK"] = KeyItem(vk: VK_SCROLL) 207 | result["BREAK"] = KeyItem(vk: VK_CANCEL) 208 | result["PAUSE"] = KeyItem(vk: VK_PAUSE) 209 | result["NUMPAD0"] = KeyItem(vk: VK_NUMPAD0) 210 | result["NUMPAD1"] = KeyItem(vk: VK_NUMPAD1) 211 | result["NUMPAD2"] = KeyItem(vk: VK_NUMPAD2) 212 | result["NUMPAD3"] = KeyItem(vk: VK_NUMPAD3) 213 | result["NUMPAD4"] = KeyItem(vk: VK_NUMPAD4) 214 | result["NUMPAD5"] = KeyItem(vk: VK_NUMPAD5) 215 | result["NUMPAD6"] = KeyItem(vk: VK_NUMPAD6) 216 | result["NUMPAD7"] = KeyItem(vk: VK_NUMPAD7) 217 | result["NUMPAD8"] = KeyItem(vk: VK_NUMPAD8) 218 | result["NUMPAD9"] = KeyItem(vk: VK_NUMPAD9) 219 | result["NUMPADMULT"] = KeyItem(vk: VK_MULTIPLY) 220 | result["NUMPADADD"] = KeyItem(vk: VK_ADD) 221 | result["NUMPADSUB"] = KeyItem(vk: VK_SUBTRACT) 222 | result["NUMPADDIV"] = KeyItem(vk: VK_DIVIDE, extend: true) 223 | result["NUMPADDOT"] = KeyItem(vk: VK_DECIMAL) 224 | result["NUMPADENTER"] = KeyItem(vk: VK_RETURN, extend: true) 225 | result["APPSKEY"] = KeyItem(vk: VK_APPS, extend: true) 226 | result["LALT"] = KeyItem(vk: VK_LMENU) 227 | result["RALT"] = KeyItem(vk: VK_RMENU, extend: true) 228 | result["LCTRL"] = KeyItem(vk: VK_LCONTROL) 229 | result["RCTRL"] = KeyItem(vk: VK_RCONTROL, extend: true) 230 | result["LSHIFT"] = KeyItem(vk: VK_LSHIFT) 231 | result["RSHIFT"] = KeyItem(vk: VK_RSHIFT, extend: true) 232 | result["SLEEP"] = KeyItem(vk: VK_SLEEP, extend: true) 233 | result["BROWSERBACK"] = KeyItem(vk: VK_BROWSER_BACK, extend: true) 234 | result["BROWSERFORWARD"] = KeyItem(vk: VK_BROWSER_FORWARD, extend: true) 235 | result["BROWSERREFRESH"] = KeyItem(vk: VK_BROWSER_REFRESH, extend: true) 236 | result["BROWSERSTOP"] = KeyItem(vk: VK_BROWSER_STOP, extend: true) 237 | result["BROWSERSEARCH"] = KeyItem(vk: VK_BROWSER_SEARCH, extend: true) 238 | result["BROWSERFAVORITES"] = KeyItem(vk: VK_BROWSER_FAVORITES, extend: true) 239 | result["BROWSERHOME"] = KeyItem(vk: VK_BROWSER_HOME, extend: true) 240 | result["VOLUMEMUTE"] = KeyItem(vk: VK_VOLUME_MUTE, extend: true) 241 | result["VOLUMEDOWN"] = KeyItem(vk: VK_VOLUME_DOWN, extend: true) 242 | result["VOLUMEUP"] = KeyItem(vk: VK_VOLUME_UP, extend: true) 243 | result["MEDIANEXT"] = KeyItem(vk: VK_MEDIA_NEXT_TRACK, extend: true) 244 | result["MEDIAPREV"] = KeyItem(vk: VK_MEDIA_PREV_TRACK, extend: true) 245 | result["MEDIASTOP"] = KeyItem(vk: VK_MEDIA_STOP, extend: true) 246 | result["MEDIAPLAYPAUSE"] = KeyItem(vk: VK_MEDIA_PLAY_PAUSE, extend: true) 247 | result["LAUNCHMAIL"] = KeyItem(vk: VK_LAUNCH_MAIL, extend: true) 248 | result["LAUNCHMEDIA"] = KeyItem(vk: VK_LAUNCH_MEDIA_SELECT, extend: true) 249 | result["LAUNCHAPP1"] = KeyItem(vk: VK_LAUNCH_APP1, extend: true) 250 | result["LAUNCHAPP2"] = KeyItem(vk: VK_LAUNCH_APP2, extend: true) 251 | result["NUMLOCK"] = KeyItem(vk: VK_NUMLOCK) 252 | result["CAPSLOCK"] = KeyItem(vk: VK_CAPITAL) 253 | result["SCROLLLOCK"] = KeyItem(vk: VK_SCROLL) 254 | 255 | proc initModifierTable(): Table[string, ModifierItem] = 256 | result["ALTDOWN"] = ModifierItem(modifier: kmLAlt, downup: kDown) 257 | result["LALTDOWN"] = ModifierItem(modifier: kmLAlt, downup: kDown) 258 | result["RALTDOWN"] = ModifierItem(modifier: kmRAlt, downup: kDown) 259 | result["ALTUP"] = ModifierItem(modifier: kmLAlt, downup: kUp) 260 | result["LALTUP"] = ModifierItem(modifier: kmLAlt, downup: kUp) 261 | result["RALTUP"] = ModifierItem(modifier: kmRAlt, downup: kUp) 262 | 263 | result["SHIFTDOWN"] = ModifierItem(modifier: kmLShift, downup: kDown) 264 | result["LSHIFTDOWN"] = ModifierItem(modifier: kmLShift, downup: kDown) 265 | result["RSHIFTDOWN"] = ModifierItem(modifier: kmRShift, downup: kDown) 266 | result["SHIFTUP"] = ModifierItem(modifier: kmLShift, downup: kUp) 267 | result["LSHIFTUP"] = ModifierItem(modifier: kmLShift, downup: kUp) 268 | result["RSHIFTUP"] = ModifierItem(modifier: kmRShift, downup: kUp) 269 | 270 | result["CTRLDOWN"] = ModifierItem(modifier: kmLControl, downup: kDown) 271 | result["LCTRLDOWN"] = ModifierItem(modifier: kmLControl, downup: kDown) 272 | result["RCTRLDOWN"] = ModifierItem(modifier: kmRControl, downup: kDown) 273 | result["CTRLUP"] = ModifierItem(modifier: kmLControl, downup: kUp) 274 | result["LCTRLUP"] = ModifierItem(modifier: kmLControl, downup: kUp) 275 | result["RCTRLUP"] = ModifierItem(modifier: kmRControl, downup: kUp) 276 | 277 | result["WINDOWN"] = ModifierItem(modifier: kmLWin, downup: kDown) 278 | result["LWINDOWN"] = ModifierItem(modifier: kmLWin, downup: kDown) 279 | result["RWINDOWN"] = ModifierItem(modifier: kmRWin, downup: kDown) 280 | result["WINUP"] = ModifierItem(modifier: kmLWin, downup: kUp) 281 | result["LWINUP"] = ModifierItem(modifier: kmLWin, downup: kUp) 282 | result["RWINUP"] = ModifierItem(modifier: kmRWin, downup: kUp) 283 | 284 | proc send(cmd: KeyCommand, window = InvalidWindow) = 285 | const 286 | keyTable = initKeyTable() 287 | modifierTable = initModifierTable() 288 | 289 | var 290 | presistModifiers {.global.}: set[KeyModifier] 291 | cmd = cmd 292 | 293 | let keyName = 294 | if cmd.text.len > 1: 295 | cmd.text.replace("_", "").toUpperAscii 296 | else: 297 | cmd.text 298 | 299 | if keyName == "ASC": 300 | # Send as unicode input 301 | sendString(+$ WCHAR(cmd.count), 1, window) 302 | 303 | elif keyName in keyTable: 304 | # Send as keystroke in keytable 305 | let item = keyTable[keyName] 306 | if item.shift: cmd.modifiers.incl kmLShift 307 | 308 | # Ignores the modifiers that are already be pressed down 309 | cmd.modifiers.excl presistModifiers 310 | 311 | if cmd.toggle in {ktDown, ktUp}: 312 | let downup = if cmd.toggle == ktDown: kDown else: kUp 313 | pressKey(item.vk, downup, item.extend, window) 314 | 315 | elif item.vk in {VK_NUMLOCK, VK_CAPITAL, VK_SCROLL} and cmd.toggle in {ktOn, ktOff, ktToggle}: 316 | toggleKey(item.vk, cmd.toggle, window) 317 | 318 | else: 319 | sendKey(item.vk, cmd.modifiers, cmd.count, item.extend, window) 320 | 321 | elif keyName in modifierTable: 322 | # Send modifier down or up 323 | let item = modifierTable[keyName] 324 | sendModifier({item.modifier}, item.downup, window) 325 | 326 | if item.downup == kDown: 327 | presistModifiers.incl item.modifier 328 | else: 329 | presistModifiers.excl item.modifier 330 | 331 | else: 332 | # Send as string 333 | sendString(+$cmd.text, cmd.count, window) 334 | 335 | proc send*(text: string, raw = false, window = InvalidWindow, attach = false, 336 | restoreCapslock = false) = 337 | ## Sends simulated keystrokes to the active window. 338 | ## If *raw* is true, keys are sent raw. 339 | ## If *window* is specified, it attempts to keep it active during send(). 340 | ## If *attach* is true, it attaches input threads when during send(). 341 | ## If *restoreCapslock* is true, the state of capslock is restored after send(). 342 | ## 343 | ## ================================ ============================================================= 344 | ## Syntax Description 345 | ## ================================ ============================================================= 346 | ## \+ Combine next key with SHIFT. 347 | ## \! Combine next key with ALT. 348 | ## \^ Combine next key with CTRL. 349 | ## \# Combine next key with Windows key. 350 | ## {\!} \! 351 | ## {\#} \# 352 | ## {\+} \+ 353 | ## {\^} \^ 354 | ## {{} { 355 | ## {}} } 356 | ## {SPACE} SPACE 357 | ## {ENTER} ENTER 358 | ## {ALT} ALT 359 | ## {BACKSPACE} or {BS} BACKSPACE 360 | ## {DELETE} or {DEL} DELETE 361 | ## {UP} Up arrow 362 | ## {DOWN} Down arrow 363 | ## {LEFT} Left arrow 364 | ## {RIGHT} Right arrow 365 | ## {HOME} HOME 366 | ## {END} END 367 | ## {ESCAPE} or {ESC} ESCAPE 368 | ## {INSERT} or {INS} INS 369 | ## {PGUP} PageUp 370 | ## {PGDN} PageDown 371 | ## {F1} - {F12} Function keys 372 | ## {TAB} TAB 373 | ## {PRINTSCREEN} Print Screen key 374 | ## {LWIN} Left Windows key 375 | ## {RWIN} Right Windows key 376 | ## {NUMLOCK on/off/toggle} NUMLOCK (on/off/toggle) 377 | ## {CAPSLOCK on/off/toggle} CAPSLOCK (on/off/toggle) 378 | ## {SCROLLLOCK on/off/toggle} SCROLLLOCK (on/off/toggle) 379 | ## {BREAK} Break 380 | ## {PAUSE} Pause 381 | ## {NUMPAD0} - {NUMPAD9} Numpad digits 382 | ## {NUMPADMULT} Numpad Multiply 383 | ## {NUMPADADD} Numpad Add 384 | ## {NUMPADSUB} Numpad Subtract 385 | ## {NUMPADDIV} Numpad Divide 386 | ## {NUMPADDOT} Numpad period 387 | ## {NUMPADENTER} Enter key on the numpad 388 | ## {APPSKEY} Windows App key 389 | ## {LALT} Left ALT key 390 | ## {RALT} Right ALT key 391 | ## {LCTRL} Left CTRL key 392 | ## {RCTRL} Right CTRL key 393 | ## {LSHIFT} Left Shift key 394 | ## {RSHIFT} Right Shift key 395 | ## {SLEEP} Computer SLEEP key 396 | ## {ALTDOWN} Holds the ALT key down until {ALTUP} 397 | ## {LALTDOWN} or {RALTDOWN} Holds the left or right ALT key down until {LALTUP} or {RALTUP} 398 | ## {SHIFTDOWN} Holds the SHIFT key down until {SHIFTUP} 399 | ## {LSHIFTDOWN} or {RSHIFTDOWN} Holds the left or right SHIFT key down until {LALTUP} or {RALTUP} 400 | ## {CTRLDOWN} Holds the CTRL key down until {CTRLUP} 401 | ## {LCTRLDOWN} or {RCTRLDOWN} Holds the left or right CTRL key down until {LCTRLUP} or {RCTRLUP} 402 | ## {WINDOWN} Holds the left Windows key down until {WINUP} 403 | ## {LWINDOWN} or {RWINDOWN} Holds the left or right Windows key down until {LWINUP} or {RWINUP} 404 | ## {ASC nnnn} Send the specified ASCII character 405 | ## {BROWSER_BACK} Select the browser "back" button 406 | ## {BROWSER_FORWARD} Select the browser "forward" button 407 | ## {BROWSER_REFRESH} Select the browser "refresh" button 408 | ## {BROWSER_STOP} Select the browser "stop" button 409 | ## {BROWSER_SEARCH} Select the browser "search" button 410 | ## {BROWSER_FAVORITES} Select the browser "favorites" button 411 | ## {BROWSER_HOME} Launch the browser and go to the home page 412 | ## {VOLUME_MUTE} Mute the volume 413 | ## {VOLUME_DOWN} Reduce the volume 414 | ## {VOLUME_UP} Increase the volume 415 | ## {MEDIA_NEXT} Select next track in media player 416 | ## {MEDIA_PREV} Select previous track in media player 417 | ## {MEDIA_STOP} Stop media player 418 | ## {MEDIA_PLAY_PAUSE} Play/pause media player 419 | ## {LAUNCH_MAIL} Launch the email application 420 | ## {LAUNCH_MEDIA} Launch media player 421 | ## {LAUNCH_APP1} Launch user app1 422 | ## {LAUNCH_APP2} Launch user app2 423 | ## {KEY n} KEY (or character) will be sent repeated n times 424 | ## ================================ ============================================================= 425 | 426 | runnableExamples: 427 | import window 428 | 429 | proc example() = 430 | send("#r") 431 | send("notepad{enter}") 432 | send("abc{BS 3}def", window=waitAny(window.className == "Notepad")) 433 | send("!fxn") 434 | 435 | let 436 | window = window 437 | (tid, pid) = (GetCurrentThreadId(), GetWindowThreadProcessId(HWND window, nil)) 438 | capslock = toggleKey(VK_CAPITAL, ktOff, window=window) 439 | 440 | if attach: 441 | AttachThreadInput(tid, pid, TRUE) 442 | 443 | defer: 444 | if attach: AttachThreadInput(tid, pid, FALSE) 445 | if restoreCapslock: toggleKey(VK_CAPITAL, capslock, window=window) 446 | 447 | if raw: 448 | sendString(+$text, window=window) 449 | return 450 | 451 | var 452 | state = KeyCommand(count: 1) 453 | isSpecial = false 454 | 455 | let p = peg "start": 456 | start <- *key 457 | key <- modifier * (special | uchar): 458 | var cmd = KeyCommand(count: 1, text: state.text, modifiers: state.modifiers) 459 | 460 | if isSpecial: 461 | # state.toggle and state.count is only vaild if isSpecial = true 462 | cmd.toggle = state.toggle 463 | cmd.count = state.count 464 | 465 | send(cmd, window) 466 | 467 | # reset the state 468 | state = KeyCommand(count: 1) 469 | isSpecial = false 470 | 471 | modifier <- *{'+', '!', '^', '#'}: 472 | if '+' in $0: state.modifiers.incl kmLShift 473 | if '!' in $0: state.modifiers.incl kmLAlt 474 | if '^' in $0: state.modifiers.incl kmLControl 475 | if '#' in $0: state.modifiers.incl kmLWin 476 | 477 | special <- '{' * *Blank * name * >?supplement * *Blank * '}': 478 | isSpecial = true 479 | 480 | supplement <- +Blank * (toggle | count) 481 | 482 | toggle <- i"ON" | i"OFF" | i"TOGGLE" | i"DOWN" | i"UP": 483 | state.toggle = case ($0).toUpperAscii 484 | of "ON": ktOn 485 | of "OFF": ktOff 486 | of "TOGGLE": ktToggle 487 | of "DOWN": ktDown 488 | of "UP": ktUp 489 | else: ktNil 490 | 491 | count <- +Digit: 492 | state.count = parseInt($0) 493 | 494 | name <- +(utf8.any - {' ', '\t', '}'}): 495 | state.text = $0 496 | 497 | uchar <- utf8.any: 498 | # if special fail, backtrack to uchar, the text will be overwrite 499 | state.text = $0 500 | 501 | discard p.match(text) 502 | 503 | proc send*(window: Window, text: string) = 504 | ## Sends a string of characters to a window. 505 | ## This window must process WM_CHAR event, for example: an editor contorl. 506 | let (tid, pid) = (GetCurrentThreadId(), GetWindowThreadProcessId(HWND window, nil)) 507 | sendStringByMessage(+$text, window) 508 | AttachThreadInput(tid, pid, FALSE) 509 | 510 | var hkData {.threadvar.}: HotkeyData 511 | 512 | proc keyProc(nCode: int32, wParam: WPARAM, lParam: LPARAM): LRESULT {.stdcall.} = 513 | var processed = false 514 | let kbd = cast[LPKBDLLHOOKSTRUCT](lParam) 515 | defer: 516 | result = if processed: LRESULT 1 else: CallNextHookEx(0, nCode, wParam, lParam) 517 | 518 | case int wParam 519 | of WM_KEYUP, WM_SYSKEYUP: 520 | hkData.lastKeyCode = 0 521 | var isMod = false 522 | 523 | case int kbd.vkCode 524 | of VK_LCONTROL, VK_RCONTROL: hkData.lastModifiers = hkData.lastModifiers and (not wModCtrl); isMod = true 525 | of VK_LMENU, VK_RMENU: hkData.lastModifiers = hkData.lastModifiers and (not wModAlt); isMod = true 526 | of VK_LSHIFT, VK_RSHIFT: hkData.lastModifiers = hkData.lastModifiers and (not wModShift); isMod = true 527 | of VK_LWIN, VK_RWIN: hkData.lastModifiers = hkData.lastModifiers and (not wModWin); isMod = true 528 | else: discard 529 | 530 | of WM_KEYDOWN, WM_SYSKEYDOWN: 531 | case int kbd.vkCode 532 | 533 | of VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU, VK_LSHIFT, VK_RSHIFT, VK_LWIN, VK_RWIN: 534 | hkData.lastKeyCode = 0 535 | case int kbd.vkCode 536 | of VK_LCONTROL, VK_RCONTROL: hkData.lastModifiers = hkData.lastModifiers or wModCtrl 537 | of VK_LMENU, VK_RMENU: hkData.lastModifiers = hkData.lastModifiers or wModAlt 538 | of VK_LSHIFT, VK_RSHIFT: hkData.lastModifiers = hkData.lastModifiers or wModShift 539 | of VK_LWIN, VK_RWIN: hkData.lastModifiers = hkData.lastModifiers or wModWin 540 | else: discard 541 | 542 | else: 543 | let keyCode = int kbd.vkCode 544 | var modifiers = 0 545 | if hkData.lastModifiers != 0: 546 | if (GetAsyncKeyState(VK_CONTROL) and 0x8000) != 0: modifiers = modifiers or wModCtrl 547 | if (GetAsyncKeyState(VK_MENU) and 0x8000) != 0: modifiers = modifiers or wModAlt 548 | if (GetAsyncKeyState(VK_SHIFT) and 0x8000) != 0: modifiers = modifiers or wModShift 549 | if (GetAsyncKeyState(VK_LWIN) and 0x8000) != 0 or (GetAsyncKeyState(VK_RWIN) and 0x8000) != 0: 550 | modifiers = modifiers or wModWin 551 | hkData.lastModifiers = modifiers 552 | 553 | if keyCode != hkData.lastKeyCode: 554 | let hotkey = (modifiers, keyCode) 555 | hkData.table.withValue(hotkey, winData): 556 | let ret = int SendMessage(winData.hwnd, WM_HOTKEY, WPARAM winData.id, MAKELPARAM(modifiers, keyCode)) 557 | if ret <= 0: 558 | processed = true 559 | 560 | hkData.lastKeyCode = keyCode 561 | 562 | else: discard 563 | 564 | proc registerHotKeyEx*(self: wWindow, id: int, hotkey: Hotkey): bool 565 | {.validate, discardable.} = 566 | ## Registers a system wide hotkey. Every time the user presses the hotkey 567 | ## registered here, the window will receive a wEvent_HotKey event. 568 | ## If the user processes wEvent_HotKey event and set a postive result 569 | ## (e.g. event.result = 1), the key will not be blocked. 570 | ## 571 | ## The difference from wNim/wWindow.registerHotKey() is that this procedure 572 | ## use low-level keyboard hook instead of RegisterHotKey API. So that the 573 | ## system default key combination can be replaced, For example: Win + R. 574 | if hkData.hHook == 0: 575 | hkData.hHook = SetWindowsHookEx(WH_KEYBOARD_LL, keyProc, GetModuleHandle(nil), 0) 576 | if hkData.hHook == 0: 577 | return false 578 | 579 | hkData.table[hotkey] = (self.getHandle, id) 580 | 581 | proc registerHotKeyEx*(self: wWindow, id: int, hotkey: string): bool 582 | {.validate, inline, discardable.} = 583 | ## Registers a system wide hotkey. Accept a hotkey string. 584 | 585 | runnableExamples: 586 | import wNim/wFrame 587 | 588 | proc example() = 589 | var frame = Frame() 590 | frame.registerHotKeyEx(0, "Ctrl + Alt + F1") 591 | 592 | result = self.registerHotKeyEx(id, wStringToHotkey(hotkey)) 593 | 594 | proc unregisterHotKeyEx*(self: wWindow, id: int): bool 595 | {.validate, discardable.} = 596 | ## Unregisters a system wide hotkey. 597 | for hotkey, winData in hkData.table: 598 | if winData == (self.getHandle, id): 599 | hkData.table.del hotkey 600 | break 601 | 602 | if hkData.table.len == 0 and hkData.hHook != 0: 603 | UnhookWindowsHookEx(hkData.hHook) 604 | hkData.hHook = 0 605 | -------------------------------------------------------------------------------- /wAuto/misc.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | ## This module contains misc. functions for wAuto. 9 | 10 | import os, strutils 11 | import winim/lean, winim/inc/shellapi 12 | import wNim/[wDataObject, wUtils] 13 | 14 | proc isAdmin*(): bool = 15 | ## Checks if the current user has full administrator privileges. 16 | var sid: PSID 17 | defer: 18 | if sid != nil: 19 | FreeSid(sid) 20 | 21 | var authority = SID_IDENTIFIER_AUTHORITY(Value: SECURITY_NT_AUTHORITY) 22 | if AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, 23 | DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &sid) == 0: 24 | return false 25 | 26 | var isMember: BOOL 27 | if CheckTokenMembership(0, sid, &isMember) != 0: 28 | return bool isMember 29 | 30 | proc requireAdmin*(raiseError = true) = 31 | ## Elevate the current process during runtime by restarting it. 32 | ## Raise an error if the user cancel it if `raiseError` is true. 33 | if not isAdmin(): 34 | var 35 | path = T(getAppFilename()) 36 | parameters = T(quoteShellCommand(commandLineParams())) 37 | sei = SHELLEXECUTEINFO( 38 | cbSize: cint sizeof(SHELLEXECUTEINFO), 39 | lpVerb: T"runas", 40 | lpFile: &path, 41 | lpParameters: ¶meters, 42 | fMask: SEE_MASK_NO_CONSOLE, 43 | nShow: SW_NORMAL) 44 | 45 | if ShellExecuteEx(&sei) == 0: 46 | if not raiseError: quit() 47 | 48 | var 49 | code = GetLastError() 50 | buffer: LPTSTR 51 | 52 | defer: 53 | LocalFree(cast[HLOCAL](buffer)) 54 | 55 | # English only, because --app:gui use the ansi version messagebox for error message 56 | FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER or 57 | FORMAT_MESSAGE_FROM_SYSTEM or 58 | FORMAT_MESSAGE_IGNORE_INSERTS, 59 | nil, code, DWORD MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), 60 | cast[LPTSTR](&buffer), 0, nil) 61 | 62 | var error = newException(OSError, $buffer) 63 | error.errorCode = code 64 | raise error 65 | 66 | else: 67 | quit() 68 | 69 | proc clipGet*(allowFiles = false): string = 70 | ## Retrieves text from the clipboard. 71 | ## When *allowFiles* is true and multiple selecting file/dir are stored in the 72 | ## clipboard, the filename/dirname are returned as texts separated by @LF. 73 | let data = wGetClipboard() 74 | if data.isText(): 75 | result = data.getText() 76 | 77 | elif data.isFiles() and allowFiles: 78 | result = data.getFiles.join("\n") 79 | 80 | proc clipPut*(text: string) = 81 | ## Writes text to the clipboard. An empty string "" will empty the clipboard. 82 | if text == "": 83 | wClearClipboard() 84 | else: 85 | wSetClipboard(DataObject(text)) 86 | wFlushClipboard() 87 | -------------------------------------------------------------------------------- /wAuto/mouse.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | ## This module contains support to simulate mouse movements and clicks. 9 | ## For all functions that receives pos (or x, y) as parameter, wDefaultPoint 10 | ## or wDefault can be specified to indicate not to change. 11 | ## 12 | ## The *speed* parameter is the speed to move the mouse in the range 1 (fastest) 13 | ## to 100 (slowest). A speed of 0 will move the mouse instantly. 14 | 15 | import tables 16 | import winim/lean except CURSORSHAPE 17 | import wNim/[wApp, wMacros, wUtils] 18 | import common, private/utils 19 | 20 | export common, wApp.wDefault, wApp.wDefaultPoint 21 | 22 | proc coordAbs(coord, n: int): int = 23 | (((65535 * coord) div (n - 1)) + 1) 24 | 25 | proc sendMouseEvent(flag: DWORD, pos: wPoint, mouseData: DWORD = 0, extra: ULONG_PTR = 0) = 26 | var input = INPUT(`type`: INPUT_MOUSE) 27 | input.mi.dx = LONG pos.x 28 | input.mi.dy = LONG pos.y 29 | input.mi.mouseData = mouseData 30 | input.mi.dwFlags = flag 31 | input.mi.dwExtraInfo = extra 32 | SendInput(1, &input, cint sizeof(INPUT)) 33 | 34 | proc getMouseMessage(mb: MouseButton): (DWORD, DWORD) = 35 | let mb = case mb 36 | of mbPrimary: 37 | if GetSystemMetrics(SM_SWAPBUTTON) == 0: mbLeft else: mbRight 38 | of mbSecondary: 39 | if GetSystemMetrics(SM_SWAPBUTTON) == 0: mbRight else: mbLeft 40 | else: mb 41 | 42 | result = case mb 43 | of mbRight: (DWORD MOUSEEVENTF_RIGHTDOWN, DWORD MOUSEEVENTF_RIGHTUP) 44 | of mbMiddle: (DWORD MOUSEEVENTF_MIDDLEDOWN, DWORD MOUSEEVENTF_MIDDLEUP) 45 | else: (DWORD MOUSEEVENTF_LEFTDOWN, DWORD MOUSEEVENTF_LEFTUP) 46 | 47 | proc getCursorPosition*(): wPoint {.property.} = 48 | ## Retrieves the current position of the mouse cursor. 49 | var p: POINT 50 | GetCursorPos(&p) 51 | result.x = int p.x 52 | result.y = int p.y 53 | 54 | proc getCursorShape*(): CursorShape {.property.} = 55 | ## Returns the current mouse cursor shape. 56 | let hwnd = GetForegroundWindow() 57 | let (tid, pid) = (GetCurrentThreadId(), GetWindowThreadProcessId(hwnd, nil)) 58 | AttachThreadInput(tid, pid, TRUE) 59 | defer: AttachThreadInput(tid, pid, FALSE) 60 | 61 | const list = [ 62 | (IDC_APPSTARTING, csAppStarting), (IDC_ARROW, csArrow), (IDC_CROSS, csCross), 63 | (IDC_HELP, csHelp), (IDC_IBEAM, csIBeam), (IDC_ICON, csIcon), (IDC_NO, csNo), 64 | (IDC_SIZE, csSize), (IDC_SIZEALL, csSizeAll), (IDC_SIZENESW, csSizeNesw), 65 | (IDC_SIZENS, csSizeNs), (IDC_SIZENWSE, csSizeNwse), (IDC_SIZEWE, csSizeWe), 66 | (IDC_UPARROW, csUpArrow), (IDC_WAIT, csWait), (IDC_HAND, csHand)] 67 | 68 | var map {.global.}: Table[HCURSOR, CursorShape] 69 | once: 70 | for (id, shape) in list: 71 | let hCursor = LoadCursor(0, id) 72 | if hCursor != 0: 73 | map[hCursor] = shape 74 | 75 | map.withValue(GetCursor(), shape) do: 76 | return shape[] 77 | do: 78 | return csUnknow 79 | 80 | proc mouseMoveRaw(pos = wDefaultPoint, speed: range[0..100] = 10): wPoint {.discardable.} = 81 | let (width, height) = wGetScreenSize() 82 | 83 | var (x0, y0) = getCursorPosition() 84 | x0 = x0.coordAbs(width) 85 | y0 = y0.coordAbs(height) 86 | 87 | var 88 | x = if pos.x == wDefault: x0 else: pos.x.coordAbs(width) 89 | y = if pos.y == wDefault: y0 else: pos.y.coordAbs(height) 90 | 91 | if speed == 0: 92 | sendMouseEvent(MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE, (x, y)) 93 | 94 | else: 95 | proc step(n1: var int, n2: int, ratio: float = 1) = 96 | var delta = (abs(n2 - n1) div speed).clamp(32, int.high) 97 | if n1 < n2: 98 | n1 = (n1 + delta).clamp(n1, n2) 99 | 100 | elif n1 > n2: 101 | n1 = (n1 - delta).clamp(n2, n1) 102 | 103 | while x0 != x or y0 != y: 104 | step(x0, x) 105 | step(y0, y) 106 | sendMouseEvent(MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE, (x0, y0)) 107 | Sleep(10) 108 | 109 | result = (x, y) 110 | 111 | proc move*(pos = wDefaultPoint, speed: range[0..100] = 10) = 112 | ## Moves the mouse pointer to *pos*. 113 | mouseMoveRaw(pos, speed) 114 | 115 | proc move*(x = wDefault, y = wDefault, speed: range[0..100] = 10) = 116 | ## Moves the mouse pointer to (x, y). 117 | move((x, y), speed) 118 | 119 | proc down*(button: MouseButton = mbLeft) = 120 | ## Perform a mouse down event at the current mouse position. 121 | let (down, _) = getMouseMessage(button) 122 | let coord = mouseMoveRaw(wDefaultPoint, 0) 123 | sendMouseEvent(MOUSEEVENTF_ABSOLUTE or down, coord) 124 | sleep(opt("mouseclickdowndelay")) 125 | 126 | proc up*(button: MouseButton = mbLeft) = 127 | ## Perform a mouse up event at the current mouse position. 128 | let (_, up) = getMouseMessage(button) 129 | let coord = mouseMoveRaw(wDefaultPoint, 0) 130 | sendMouseEvent(MOUSEEVENTF_ABSOLUTE or up, coord) 131 | sleep(opt("mouseclickdelay")) 132 | 133 | proc click*(button: MouseButton = mbLeft, pos = wDefaultPoint, clicks = 1, speed: range[0..100] = 10) = 134 | ## Perform a mouse click operation at the position *pos*. 135 | ## *clicks* is the number of times to click the mouse. 136 | let (down, up) = getMouseMessage(button) 137 | let coord = mouseMoveRaw(pos, speed) 138 | for i in 0 ..< clicks: 139 | sendMouseEvent(MOUSEEVENTF_ABSOLUTE or down, coord) 140 | sleep(opt("mouseclickdowndelay")) 141 | sendMouseEvent(MOUSEEVENTF_ABSOLUTE or up, coord) 142 | sleep(opt("mouseclickdelay")) 143 | 144 | proc click*(button: MouseButton, x = wDefault, y = wDefault, clicks = 1, speed: range[0..100] = 10) = 145 | ## Perform a mouse click operation at the position (x, y). 146 | ## *clicks* is the number of times to click the mouse. 147 | click(button, (x, y), clicks, speed) 148 | 149 | proc clickDrag*(button: MouseButton = mbLeft, pos1 = wDefaultPoint, pos2 = wDefaultPoint, speed: range[0..100] = 10) = 150 | ## Perform a mouse click and drag operation from *pos1* to *pos2*. 151 | let (down, up) = getMouseMessage(button) 152 | var coord = mouseMoveRaw(pos1, speed) 153 | sendMouseEvent(MOUSEEVENTF_ABSOLUTE or down, coord) 154 | sleep(opt("mouseclickdragdelay")) 155 | 156 | coord = mouseMoveRaw(pos2, speed) 157 | sendMouseEvent(MOUSEEVENTF_ABSOLUTE or up, coord) 158 | sleep(opt("mouseclickdragdelay")) 159 | 160 | proc wheelUp*(clicks = 1) = 161 | ## Moves the mouse wheel up. 162 | ## *clicks* is the number of times to move the wheel. 163 | var coord = mouseMoveRaw(wDefaultPoint, 0) 164 | for i in 0 ..< clicks: 165 | sendMouseEvent(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_WHEEL, coord, mouseData=WHEEL_DELTA) 166 | sleep(opt("mouseclickdelay")) 167 | 168 | proc wheelDown*(clicks = 1) = 169 | ## Moves the mouse wheel down. 170 | ## *clicks* is the number of times to move the wheel. 171 | var coord = mouseMoveRaw(wDefaultPoint, 0) 172 | for i in 0 ..< clicks: 173 | sendMouseEvent(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_WHEEL, coord, mouseData=(-WHEEL_DELTA)) 174 | sleep(opt("mouseclickdelay")) 175 | -------------------------------------------------------------------------------- /wAuto/private/utils.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | import winim/lean 9 | 10 | type 11 | RemotePointer* = object 12 | handle*: HANDLE 13 | address*: pointer 14 | size*: Natural 15 | 16 | template sleep*(n: int) = Sleep(DWORD n) 17 | 18 | proc setPrivilege*(privilege = "SeDebugPrivilege") = 19 | var 20 | token: HANDLE 21 | tp = TOKEN_PRIVILEGES( 22 | PrivilegeCount: 1, 23 | Privileges: [LUID_AND_ATTRIBUTES(Attributes: SE_PRIVILEGE_ENABLED)]) 24 | 25 | if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, addr token): 26 | defer: 27 | CloseHandle(token) 28 | 29 | if LookupPrivilegeValue(nil, privilege, addr tp.Privileges[0].Luid): 30 | AdjustTokenPrivileges(token, false, addr tp, cint sizeof(TOKEN_PRIVILEGES), nil, nil) 31 | 32 | proc remoteAlloc*(hwnd: HWND, size: Natural): RemotePointer = 33 | var pid: DWORD 34 | GetWindowThreadProcessId(hwnd, &pid) 35 | if pid == 0: return 36 | 37 | result.handle = OpenProcess(PROCESS_VM_OPERATION or PROCESS_VM_READ or PROCESS_VM_WRITE, FALSE, pid) 38 | if result.handle == 0: return 39 | 40 | result.address = VirtualAllocEx(result.handle, nil, SIZE_T size, MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE) 41 | if result.address.isNil: return 42 | 43 | result.size = size 44 | 45 | proc remoteDealloc*(rp: var RemotePointer) = 46 | VirtualFreeEx(rp.handle, rp.address, 0, MEM_RELEASE) 47 | CloseHandle(rp.handle) 48 | rp.size = 0 49 | rp.handle = 0 50 | rp.address = nil 51 | 52 | proc remoteRead*(rp: RemotePointer): string = 53 | var bytesRead: SIZE_T 54 | result.setLen(rp.size) 55 | ReadProcessMemory(rp.handle, rp.address, addr result[0], SIZE_T rp.size, addr bytesRead) 56 | result.setLen(bytesRead) 57 | 58 | proc ok*(rp: RemotePointer): bool {.inline.} = 59 | not rp.address.isNil 60 | -------------------------------------------------------------------------------- /wAuto/process.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | ## This module contains support to manipulate process. 9 | 10 | import strutils, tables 11 | import winim/lean, winim/inc/[tlhelp32, psapi, shellapi] 12 | import wNim/wMacros 13 | import common, window, private/utils 14 | 15 | export common, lean.STILL_ACTIVE 16 | 17 | proc NtSuspendProcess(hProcess: HANDLE): LONG {.stdcall, dynlib: "ntdll", importc.} 18 | proc NtResumeProcess(hProcess: HANDLE): LONG {.stdcall, dynlib: "ntdll", importc.} 19 | 20 | type 21 | Pipes = object 22 | stdinRead: HANDLE 23 | stdinWrite: HANDLE 24 | stdoutRead: HANDLE 25 | stdoutWrite: HANDLE 26 | stderrRead: HANDLE 27 | stderrWrite: HANDLE 28 | 29 | var gPipe {.threadvar.}: Table[Process, Pipes] 30 | 31 | proc `[]`[T](x: T, U: typedesc): U = 32 | # syntax sugar for cast 33 | cast[U](x) 34 | 35 | proc `{}`[T](x: T, U: typedesc): U = 36 | # syntax sugar for zero extends cast 37 | when sizeof(x) == 1: x[uint8][U] 38 | elif sizeof(x) == 2: x[uint16][U] 39 | elif sizeof(x) == 4: x[uint32][U] 40 | elif sizeof(x) == 8: x[uint64][U] 41 | else: {.fatal.} 42 | 43 | template `{}`[T](p: T, x: SomeInteger): T = 44 | # syntax sugar for pointer (or any other type) arithmetics 45 | cast[T]((cast[int](p) +% x{int})) 46 | 47 | proc getName*(process: Process): string 48 | 49 | proc `$`*(x: Process): string {.borrow.} 50 | ## The stringify operator for a process. 51 | 52 | proc `==`*(x, y: Process): bool {.borrow.} 53 | ## Checks for equality between two process. 54 | 55 | proc repr*(x: Process): string = 56 | ## Returns string representation of a process. 57 | result = "Process(name: " 58 | result.add x.getName.escape 59 | result.add ", pid: " 60 | result.add $x 61 | result.add ")" 62 | 63 | proc getCurrentProcess*(): Process {.property, inline.} = 64 | return Process GetCurrentProcessId() 65 | 66 | iterator processes*(): tuple[name: string, process: Process] = 67 | ## Iterates over all processes. 68 | let handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 69 | if handle != INVALID_HANDLE_VALUE: 70 | defer: CloseHandle(handle) 71 | 72 | var entry = PROCESSENTRY32(dwSize: cint sizeof(PROCESSENTRY32)) 73 | if Process32First(handle, &entry) != FALSE: 74 | while true: 75 | yield ((%$(entry.szExeFile)).nullTerminated, Process entry.th32ProcessID) 76 | if Process32Next(handle, &entry) == FALSE: 77 | break 78 | 79 | iterator processes*(name: string): Process = 80 | ## Iterates over process of specified name. 81 | 82 | runnableExamples: 83 | proc example() = 84 | for process in processes("notepad.exe"): 85 | waitClose(process) 86 | 87 | var name = name.toLowerAscii 88 | for tup in processes(): 89 | if tup.name.toLowerAscii == name: 90 | yield tup.process 91 | 92 | proc getHandle*(process: Process): DWORD {.property, inline.} = 93 | ## Gets the Win32 process ID (PID) from the specified process. 94 | result = DWORD process 95 | 96 | proc isExists*(process: Process): bool = 97 | ## Checks to see if a specified process exists. 98 | 99 | proc isExists1(process: Process): bool = 100 | # Fast, but may return true on some non-exists pid 101 | if process == Process 0: # System Idle Process 102 | return true 103 | 104 | let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, DWORD process) 105 | if handle != 0: 106 | defer: 107 | CloseHandle(handle) 108 | 109 | var exitCode: DWORD 110 | if GetExitCodeProcess(handle, &exitCode) != 0: 111 | return exitCode == STILL_ACTIVE 112 | 113 | else: 114 | return true 115 | 116 | else: 117 | return GetLastError() != ERROR_INVALID_PARAMETER 118 | 119 | proc isExists2(process: Process): bool = 120 | # Slow, but exactly 121 | if process == Process 0: # System Idle Process 122 | return true 123 | 124 | var 125 | processes = newSeq[DWORD](4096) 126 | needed: DWORD = 0 127 | 128 | while true: 129 | let size = cint(sizeof(DWORD) * processes.len) 130 | if EnumProcesses(addr processes[0], size, &needed) == 0: 131 | break 132 | 133 | if DWORD size == needed: 134 | processes.setLen(processes.len * 2) 135 | 136 | else: 137 | break 138 | 139 | for i in 0 ..< (needed div sizeof(DWORD)): 140 | if process == Process processes[i]: 141 | return true 142 | 143 | return isExists1(process) and isExists2(process) 144 | 145 | proc isProcessExists*(name: string): bool = 146 | ## Checks to see if a specified process exists. 147 | for process in processes(name): 148 | return true 149 | 150 | proc isWow64*(process: Process): bool = 151 | ## Determines whether the specified process is running under WOW64 or an Intel64 of x64 processor. 152 | let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, DWORD process) 153 | if handle != 0: 154 | defer: CloseHandle(handle) 155 | var wow64: BOOL 156 | if IsWow64Process(handle, &wow64) != 0: 157 | result = bool wow64 158 | 159 | proc getProcess*(name: string): Process {.property.} = 160 | ## Returns the process of specified name or InvalidProcess if not found. 161 | for process in processes(name): 162 | return process 163 | 164 | return InvalidProcess 165 | 166 | proc getStats*(process: Process): ProcessStats {.property.} = 167 | ## Returns Memory and IO infos of a running process. 168 | setPrivilege("SeDebugPrivilege") 169 | let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, DWORD process) 170 | if handle != 0: 171 | defer: CloseHandle(handle) 172 | var 173 | ioCounters: IO_COUNTERS 174 | memCounters: PROCESS_MEMORY_COUNTERS 175 | 176 | if GetProcessIoCounters(handle, &ioCounters) != 0: 177 | result.readOperationCount = ioCounters.ReadOperationCount 178 | result.writeOperationCount = ioCounters.WriteOperationCount 179 | result.otherOperationCount = ioCounters.OtherOperationCount 180 | result.readTransferCount = ioCounters.ReadTransferCount 181 | result.writeTransferCount = ioCounters.WriteTransferCount 182 | result.otherTransferCount = ioCounters.OtherTransferCount 183 | 184 | if GetProcessMemoryInfo(handle, &memCounters, cint sizeof(memCounters)) != 0: 185 | result.pageFaultCount = memCounters.PageFaultCount 186 | result.peakWorkingSetSize = memCounters.PeakWorkingSetSize 187 | result.workingSetSize = memCounters.WorkingSetSize 188 | result.quotaPeakPagedPoolUsage = memCounters.QuotaPeakPagedPoolUsage 189 | result.quotaPagedPoolUsage = memCounters.QuotaPagedPoolUsage 190 | result.quotaPeakNonPagedPoolUsage = memCounters.QuotaPeakNonPagedPoolUsage 191 | result.quotaNonPagedPoolUsage = memCounters.QuotaNonPagedPoolUsage 192 | result.pagefileUsage = memCounters.PagefileUsage 193 | result.peakPagefileUsage = memCounters.PeakPagefileUsage 194 | 195 | result.gdiObjects = GetGuiResources(handle, GR_GDIOBJECTS) 196 | result.userObjects = GetGuiResources(handle, GR_USEROBJECTS) 197 | 198 | proc kill*(process: Process): bool {.discardable.} = 199 | ## Terminates a process. 200 | setPrivilege("SeDebugPrivilege") 201 | let handle = OpenProcess(PROCESS_TERMINATE, 0, DWORD process) 202 | if handle != 0: 203 | defer: CloseHandle(handle) 204 | if TerminateProcess(handle, 0) != 0: 205 | return true 206 | 207 | proc suspend*(process: Process): bool {.discardable.} = 208 | ## Suspend a process. 209 | setPrivilege("SeDebugPrivilege") 210 | # PROCESS_SUSPEND_RESUME may not work 211 | let handle = OpenProcess(PROCESS_ALL_ACCESS, 0, DWORD process) 212 | if handle != 0: 213 | defer: CloseHandle(handle) 214 | return NtSuspendProcess(handle) == 0 215 | 216 | proc resume*(process: Process): bool {.discardable.} = 217 | ## Resume a process. 218 | setPrivilege("SeDebugPrivilege") 219 | let handle = OpenProcess(PROCESS_ALL_ACCESS, 0, DWORD process) 220 | if handle != 0: 221 | defer: CloseHandle(handle) 222 | return NtResumeProcess(handle) == 0 223 | 224 | proc killProcess*(name: string) = 225 | ## Terminates all processes with the same name. 226 | var name = name.toLowerAscii 227 | for process in processes(name): 228 | kill(process) 229 | 230 | proc waitProcess*(name: string, timeout = 0.0): Process {.discardable.} = 231 | ## Pauses until a given process exists. 232 | ## *timeout* specifies how long to wait (in seconds). Default (0.0) is to wait indefinitely. 233 | ## Returns the process or InvalidProcess if timeout reached. 234 | var timer = GetTickCount() 235 | while timeout == 0.0 or float(GetTickCount() -% timer) < timeout * 1000: 236 | for process in processes(name): 237 | return process 238 | 239 | Sleep(250) 240 | 241 | result = InvalidProcess 242 | 243 | proc waitClose*(process: Process, timeout = 0.0): DWORD {.discardable.} = 244 | ## Pauses until a given process does not exist. 245 | ## *timeout* specifies how long to wait (in seconds). Default (0.0) is to wait indefinitely. 246 | ## Returns exit code of the process or STILL_ACTIVE(259) if timeout reached. 247 | setPrivilege("SeDebugPrivilege") 248 | var 249 | timeout = if timeout == 0.0: INFINITE else: cint timeout * 1000 250 | handle = OpenProcess(PROCESS_QUERY_INFORMATION or SYNCHRONIZE, 0, DWORD process) 251 | 252 | if handle != 0: 253 | WaitForSingleObject(handle, timeout) 254 | GetExitCodeProcess(handle, &result) 255 | CloseHandle(handle) 256 | 257 | proc setPriority*(process: Process, priority: ProcessPriority): bool {.property, discardable.} = 258 | ## Changes the priority of a process. 259 | setPrivilege("SeDebugPrivilege") 260 | let handle = OpenProcess(PROCESS_SET_INFORMATION, 0, DWORD process) 261 | if handle != 0: 262 | defer: CloseHandle(handle) 263 | let priorityClass = case priority 264 | of ppIdle: IDLE_PRIORITY_CLASS 265 | of ppBelowNormal: BELOW_NORMAL_PRIORITY_CLASS 266 | of ppNormal: NORMAL_PRIORITY_CLASS 267 | of ppAboveNormal: ABOVE_NORMAL_PRIORITY_CLASS 268 | of ppHigh: HIGH_PRIORITY_CLASS 269 | of ppRealtime: REALTIME_PRIORITY_CLASS 270 | else: 0 271 | 272 | if priorityClass != 0: 273 | result = SetPriorityClass(handle, DWORD priorityClass) != 0 274 | 275 | proc getPriority*(process: Process): ProcessPriority {.property.} = 276 | ## Gets the priority of a process. 277 | setPrivilege("SeDebugPrivilege") 278 | result = ppError 279 | let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, DWORD process) 280 | if handle != 0: 281 | defer: CloseHandle(handle) 282 | result = case GetPriorityClass(handle) 283 | of IDLE_PRIORITY_CLASS: ppIdle 284 | of BELOW_NORMAL_PRIORITY_CLASS: ppBelowNormal 285 | of NORMAL_PRIORITY_CLASS: ppNormal 286 | of ABOVE_NORMAL_PRIORITY_CLASS: ppAboveNormal 287 | of HIGH_PRIORITY_CLASS: ppHigh 288 | of REALTIME_PRIORITY_CLASS: ppRealtime 289 | else: ppError 290 | 291 | proc getPath*(process: Process): string {.property.} = 292 | ## Gets the path of a process. 293 | setPrivilege("SeDebugPrivilege") 294 | let handle = OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, 0, DWORD process) 295 | if handle != 0: 296 | defer: CloseHandle(handle) 297 | # use QueryFullProcessImageName first 298 | # https://stackoverflow.com/questions/20792076/ 299 | var 300 | buffer = T(MAX_PATH) 301 | size: DWORD = MAX_PATH 302 | 303 | if QueryFullProcessImageName(handle, 0, &buffer, &size) != 0: 304 | buffer.setLen(size) 305 | result = $buffer 306 | else: 307 | buffer.setLen(GetModuleFileNameEx(handle, 0, &buffer, MAX_PATH)) 308 | result = $buffer 309 | 310 | proc getCommandLine*(process: Process): string {.property.} = 311 | ## Gets the command line of a process. 312 | 313 | proc getCommandLine(handle: HANDLE): string = 314 | var 315 | pbi: PROCESS_BASIC_INFORMATION 316 | prupp: PRTL_USER_PROCESS_PARAMETERS 317 | commandLine: UNICODE_STRING 318 | 319 | if NtQueryInformationProcess(handle, 0, &pbi, sizeof(pbi), nil) != S_OK: return 320 | 321 | if ReadProcessMemory(handle, 322 | pbi.PebBaseAddress{offsetof(PEB, ProcessParameters)}, 323 | &prupp, sizeof(prupp), nil) == 0: return 324 | 325 | if ReadProcessMemory(handle, 326 | prupp{offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine)}, 327 | &commandLine, sizeof(commandLine), nil) == 0: return 328 | 329 | var buffer = newString(commandLine.Length + 2) 330 | if ReadProcessMemory(handle, 331 | commandLine.Buffer, 332 | &buffer, SIZE_T commandLine.Length, nil) == 0: return 333 | 334 | result = $cast[LPWSTR](&buffer) 335 | 336 | when winimCpu32: 337 | type 338 | PROCESS_BASIC_INFORMATION64 {.pure.} = object 339 | Reserved1: int64 340 | PebBaseAddress: int64 341 | Reserved2: array[4, int64] 342 | 343 | proc NtWow64QueryInformationProcess64(ProcessHandle: HANDLE, 344 | ProcessInformationClass: PROCESSINFOCLASS, 345 | ProcessInformation: PVOID, 346 | ProcessInformationLength: ULONG, 347 | ReturnLength: PULONG): NTSTATUS 348 | {.stdcall, dynlib: "ntdll", importc.} 349 | 350 | proc NtWow64ReadVirtualMemory64( 351 | hProcess: HANDLE, 352 | lpBaseAddress: int64, 353 | lpBuffer: LPVOID, 354 | nSize: ULONG64, 355 | lpNumberOfBytesRead: PULONG64): NTSTATUS 356 | {.stdcall, dynlib: "ntdll", importc.} 357 | 358 | proc getCommandLineWow64(handle: HANDLE): string = 359 | var 360 | pbi: PROCESS_BASIC_INFORMATION64 361 | prupp: int64 362 | commandLine: UNICODE_STRING64 363 | 364 | if NtWow64QueryInformationProcess64(handle, 0, &pbi, sizeof(pbi), nil) != S_OK: return 365 | 366 | if NtWow64ReadVirtualMemory64(handle, 367 | pbi.PebBaseAddress +% 0x20, 368 | &prupp, sizeof(prupp), nil) != 0: return 369 | 370 | if NtWow64ReadVirtualMemory64(handle, 371 | prupp +% 0x70, 372 | &commandLine, sizeof(commandLine), nil) != 0: return 373 | 374 | var buffer = newString(commandLine.Length) 375 | if NtWow64ReadVirtualMemory64(handle, 376 | commandLine.Buffer, 377 | &buffer, SIZE_T commandLine.Length, nil) != 0: return 378 | 379 | result = $cast[LPWSTR](&buffer) 380 | 381 | setPrivilege("SeDebugPrivilege") 382 | let handle = OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, 0, DWORD process) 383 | if handle != 0: 384 | defer: CloseHandle(handle) 385 | 386 | when winimCpu32: 387 | if not process.isWow64 and currentProcess().isWow64: 388 | return getCommandLineWow64(handle) 389 | else: 390 | return getCommandLine(handle) 391 | else: 392 | return getCommandLine(handle) 393 | 394 | proc getName*(process: Process): string {.property.} = 395 | ## Gets the name of a process. 396 | setPrivilege("SeDebugPrivilege") 397 | let handle = OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, 0, DWORD process) 398 | if handle != 0: 399 | defer: CloseHandle(handle) 400 | var buffer = T(MAX_PATH) 401 | buffer.setLen(GetModuleBaseName(handle, 0, &buffer, MAX_PATH)) 402 | result = $buffer 403 | 404 | else: 405 | for tup in processes(): 406 | if process == tup.process: 407 | return tup.name 408 | 409 | proc close(pipes: var Pipes, options: set[ProcessOption]) = 410 | if poStdin in options: 411 | CloseHandle(pipes.stdinRead); pipes.stdinRead = 0 412 | CloseHandle(pipes.stdinWrite); pipes.stdinWrite = 0 413 | 414 | if poStdout in options: 415 | CloseHandle(pipes.stdoutRead); pipes.stdoutRead = 0 416 | CloseHandle(pipes.stdoutWrite); pipes.stdoutWrite = 0 417 | 418 | if poStderr in options: 419 | CloseHandle(pipes.stderrRead); pipes.stderrRead = 0 420 | CloseHandle(pipes.stderrWrite); pipes.stderrWrite = 0 421 | 422 | proc stdioClose*(process: Process, options: set[ProcessOption] = {}) = 423 | ## Closes resources associated with a process previously run with STDIO redirection. 424 | if process in gPipe: 425 | var pipes = gPipe[process] 426 | var options = options 427 | if options == {}: options = {poStdin, poStdout, poStderr} 428 | if poStderrMerged in options: options.incl {poStdout, poStderr} 429 | 430 | pipes.close(options) 431 | 432 | if pipes == default(Pipes): 433 | gPipe.del(process) 434 | else: 435 | gPipe[process] = pipes 436 | 437 | proc stdinWrite*(process: Process, data: string): int {.discardable} = 438 | ## Writes to the STDIN stream of a previously run child process. 439 | if process in gPipe: 440 | let pipes = gPipe[process] 441 | var written: DWORD 442 | WriteFile(pipes.stdinWrite, &data, data.len, &written, nil) 443 | return int written 444 | 445 | proc stdread(handle: HANDLE, peek = false): string = 446 | var read, total: DWORD 447 | if PeekNamedPipe(handle, nil, 0, nil, &total, nil) != 0: 448 | if total != 0: 449 | result = newString(total) 450 | 451 | if peek: 452 | PeekNamedPipe(handle, &result, cint result.len, &read, nil, nil) 453 | result.setLen(read) 454 | 455 | else: 456 | ReadFile(handle, &result, cint result.len, &read, nil) 457 | result.setLen(read) 458 | 459 | proc stdoutRead*(process: Process, peek = false): string = 460 | ## Reads from the STDOUT stream of a previously run child process. 461 | if process in gPipe: 462 | let pipes = gPipe[process] 463 | result = stdread(pipes.stdoutRead, peek) 464 | 465 | proc stderrRead*(process: Process, peek = false): string = 466 | ## Reads from the STDERR stream of a previously run child process. 467 | if process in gPipe: 468 | let pipes = gPipe[process] 469 | result = stdread(pipes.stderrRead, peek) 470 | 471 | proc run(path: string, username = "", password = "", domain = "", 472 | workingDir = "", options: set[ProcessOption] = {}): Process = 473 | 474 | var 475 | si = STARTUPINFO(cb: cint sizeof(STARTUPINFO), 476 | dwFlags: STARTF_USESHOWWINDOW, 477 | wShowWindow: SW_SHOWDEFAULT, 478 | hStdInput: GetStdHandle(STD_INPUT_HANDLE), 479 | hStdOutput: GetStdHandle(STD_OUTPUT_HANDLE), 480 | hStdError: GetStdHandle(STD_ERROR_HANDLE)) 481 | 482 | pi: PROCESS_INFORMATION 483 | 484 | sa = SECURITY_ATTRIBUTES(nLength: cint sizeof(SECURITY_ATTRIBUTES), 485 | bInheritHandle: true) 486 | 487 | creationFlags: DWORD = NORMAL_PRIORITY_CLASS or CREATE_UNICODE_ENVIRONMENT 488 | logonFlags: DWORD = 0 489 | 490 | pipes: Pipes 491 | inheritHandle = false 492 | 493 | defer: 494 | if result == InvalidProcess: 495 | pipes.close({poStdin, poStdout, poStderr}) 496 | 497 | else: 498 | CloseHandle(pi.hProcess) 499 | CloseHandle(pi.hThread) 500 | if inheritHandle: 501 | gPipe[result] = pipes 502 | 503 | result = InvalidProcess 504 | 505 | if poMinimize in options: si.wShowWindow = SW_MINIMIZE 506 | if poMaximize in options: si.wShowWindow = SW_MAXIMIZE 507 | if poHide in options: si.wShowWindow = SW_HIDE 508 | if poShow in options: si.wShowWindow = SW_SHOW 509 | 510 | if poLogonNetwork in options: logonFlags = LOGON_NETCREDENTIALS_ONLY 511 | if poLogonProfile in options: logonFlags = LOGON_WITH_PROFILE 512 | 513 | if poCreateNewConsole in options: 514 | creationFlags = creationFlags or CREATE_NEW_CONSOLE 515 | 516 | if poStdin in options: 517 | if CreatePipe(&pipes.stdinRead, &pipes.stdinWrite, &sa, 0) == 0: 518 | return InvalidProcess 519 | 520 | SetHandleInformation(pipes.stdinWrite, HANDLE_FLAG_INHERIT, 0) 521 | si.hStdInput = pipes.stdinRead 522 | inheritHandle = true 523 | si.dwFlags = si.dwFlags or STARTF_USESTDHANDLES 524 | 525 | if poStdout in options or poStderrMerged in options: 526 | if CreatePipe(&pipes.stdoutRead, &pipes.stdoutWrite, &sa, 0) == 0: 527 | return InvalidProcess 528 | 529 | SetHandleInformation(pipes.stdoutRead, HANDLE_FLAG_INHERIT, 0) 530 | si.hStdOutput = pipes.stdoutWrite 531 | if poStderrMerged in options: si.hStdError = pipes.stdoutWrite 532 | inheritHandle = true 533 | si.dwFlags = si.dwFlags or STARTF_USESTDHANDLES 534 | 535 | if poStderr in options and poStderrMerged notin options: 536 | if CreatePipe(&pipes.stderrRead, &pipes.stderrWrite, &sa, 0) == 0: 537 | return InvalidProcess 538 | 539 | SetHandleInformation(pipes.stderrRead, HANDLE_FLAG_INHERIT, 0) 540 | si.hStdError = pipes.stderrWrite 541 | inheritHandle = true 542 | si.dwFlags = si.dwFlags or STARTF_USESTDHANDLES 543 | 544 | if username == "" and password == "": 545 | var dir: LPTSTR 546 | if workingDir.len != 0: 547 | dir = T(workingDir) 548 | 549 | if CreateProcess(nil, path, nil, nil, inheritHandle, creationFlags, nil, 550 | dir, &si, &pi) != 0: 551 | result = Process pi.dwProcessId 552 | 553 | else: 554 | var dir: LPCWSTR 555 | if workingDir.len != 0: 556 | dir = +$workingDir 557 | 558 | if CreateProcessWithLogonW(username, domain, password, logonFlags, 559 | nil, path, creationFlags, nil, dir, &si, &pi) != 0: 560 | result = Process pi.dwProcessId 561 | 562 | proc run*(path: string, workingDir = "", options: set[ProcessOption] = {}): Process 563 | {.discardable.} = 564 | ## Runs an external program. 565 | ## Returns the process or InvalidProcess if error occured. 566 | run(path, username="", password="", domain="", workingDir=workingDir, options=options) 567 | 568 | proc runAs*(path: string, username: string, password: string, domain = "", 569 | workingDir = "", options: set[ProcessOption] = {}): Process {.discardable.} = 570 | ## Runs an external program under the context of a different user. 571 | ## Returns the process or InvalidProcess if error occured. 572 | run(path, username, password, domain, workingDir, options) 573 | 574 | proc runWait*(path: string, workingDir = "", options: set[ProcessOption] = {}, 575 | timeout = 0.0): DWORD {.discardable.} = 576 | ## Runs an external program and pauses execution until the program finishes. 577 | ## Returns exit code of the process or STILL_ACTIVE(259) if timeout reached. 578 | let pid = run(path, workingDir, options) 579 | result = waitClose(pid, timeout) 580 | stdioClose(pid) 581 | 582 | proc runAsWait*(path: string, username: string, password: string, domain = "", 583 | workingDir = "", options: set[ProcessOption] = {}, timeout = 0.0): DWORD 584 | {.discardable.} = 585 | ## Runs an external program under the context of a different user and pauses 586 | ## execution until the program finishes. 587 | ## Returns exit code of the process or STILL_ACTIVE(259) if timeout reached. 588 | let pid = runAs(path, username, password, domain, workingDir, options) 589 | result = waitClose(pid, timeout) 590 | stdioClose(pid) 591 | 592 | proc shellExecute*(file: string, parameters = "", workingdir = "", verb = "", 593 | show: ProcessOption = poShow): Process {.discardable.} = 594 | ## Runs an external program using the ShellExecute API. 595 | var info = SHELLEXECUTEINFO(cbSize: cint sizeof(SHELLEXECUTEINFO)) 596 | info.lpFile = file 597 | info.lpParameters = parameters 598 | info.lpDirectory = workingdir 599 | info.lpVerb = verb 600 | info.fMask = SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI 601 | info.nShow = case show 602 | of poHide: SW_HIDE 603 | of poMaximize: SW_MAXIMIZE 604 | of poMinimize: SW_MINIMIZE 605 | else: SW_SHOW 606 | 607 | if ShellExecuteEx(&info): 608 | defer: CloseHandle(info.hProcess) 609 | var pid = GetProcessId(info.hProcess) 610 | if pid != 0: 611 | return Process pid 612 | 613 | return InvalidProcess 614 | 615 | proc shellExecuteWait*(file: string, parameters = "", workingdir = "", verb = "", 616 | show: ProcessOption = poShow, timeout = 0.0): DWORD {.discardable.} = 617 | ## Runs an external program using the ShellExecute API and 618 | ## pauses script execution until it finishes. 619 | ## Returns exit code of the process or STILL_ACTIVE(259) if timeout reached. 620 | let pid = shellExecute(file, parameters, workingdir, verb, show) 621 | result = waitClose(pid, timeout) 622 | 623 | iterator windows*(process: Process): Window = 624 | ## Iterates over all top-level windows that created by the specified process. 625 | for window in windows(): 626 | if window.getProcess == process: 627 | yield window 628 | 629 | iterator allWindows*(process: Process): Window = 630 | ## Iterates over all windows that created by the specified process. 631 | for window in allWindows(): 632 | if window.getProcess == process: 633 | yield window 634 | -------------------------------------------------------------------------------- /wAuto/registry.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | ## This module contains support to manipulate Windows registry. 9 | ## 10 | ## A registry key must start with "HKEY_LOCAL_MACHINE" ("HKLM") or "HKEY_USERS" ("HKU") or 11 | ## "HKEY_CURRENT_USER" ("HKCU") or "HKEY_CLASSES_ROOT" ("HKCR") or "HKEY_CURRENT_CONFIG" ("HKCC"). 12 | ## 13 | ## When running on 64-bit Windows if you want to read a value specific to the 64-bit environment 14 | ## you have to suffix the HK... with 64 i.e. HKLM64. 15 | ## 16 | ## To access the (Default) value use "" (an empty string) for the value name. 17 | ## 18 | ## When reading a REG_MULTI_SZ key the multiple entries are separated by '\\0' - use with .split('\\0') 19 | ## to get a seq of each entry. 20 | ## 21 | ## It is possible to access remote registries by using a keyname in the form *r"\\\\computername\\keyname"*. 22 | ## To use this feature you must have the correct access rights. 23 | 24 | import strutils, endians 25 | import winim/lean, winim/inc/shellapi 26 | import common 27 | 28 | export common 29 | 30 | type 31 | RegRight = enum 32 | rrRead 33 | rrWrite 34 | rrDelete 35 | 36 | proc `==`*(a, b: RegData): bool = 37 | ## Checks for equality between two RegData variables. 38 | if a.kind != b.kind: return false 39 | if a.kind == rkRegError: return true 40 | if a.kind in {rkRegDword, rkRegDwordBigEndian}: return a.dword == b.dword 41 | if a.kind == rkRegQword: return a.qword == b.qword 42 | return a.data == b.data 43 | 44 | proc regOpen(key: string, right: RegRight): HKEY = 45 | var 46 | key = key 47 | machine = "" 48 | 49 | if key.startsWith r"\\": 50 | key.removePrefix('\\') 51 | var parts = key.split('\\', maxsplit=1) 52 | machine = r"\\" & parts[0] 53 | key = if parts.len >= 2: parts[1] else: "" 54 | 55 | var 56 | phkey: HKEY 57 | phkeyNeedClose = false 58 | parts = key.split('\\', maxsplit=1) 59 | root = parts[0].toUpperAscii 60 | sam = REGSAM (if right == rrRead: KEY_READ else: KEY_WRITE) or 61 | (if root.endsWith "64": KEY_WOW64_64KEY else: 0) 62 | 63 | defer: 64 | if phkeyNeedClose: RegCloseKey(phkey) 65 | 66 | case root 67 | of "HKEY_LOCAL_MACHINE", "HKLM", "HKEY_LOCAL_MACHINE64", "HKLM64": 68 | phkey = HKEY_LOCAL_MACHINE 69 | 70 | of "HKEY_USERS", "HKU", "HKEY_USERS64", "HKU64": 71 | phkey = HKEY_USERS 72 | 73 | of "HKEY_CURRENT_USER", "HKCU", "HKEY_CURRENT_USER64", "HKCU64": 74 | phkey = HKEY_CURRENT_USER 75 | 76 | of "HKEY_CLASSES_ROOT", "HKCR", "HKEY_CLASSES_ROOT64", "HKCR64": 77 | phkey = HKEY_CLASSES_ROOT 78 | 79 | of "HKEY_CURRENT_CONFIG", "HKCC", "HKEY_CURRENT_CONFIG64", "HKCC64": 80 | phkey = HKEY_CURRENT_CONFIG 81 | 82 | else: 83 | return 0 84 | 85 | if machine != "": 86 | if RegConnectRegistry(machine, phkey, &phkey) == ERROR_SUCCESS: 87 | phkeyNeedClose = true 88 | 89 | else: 90 | return 0 91 | 92 | elif phkey == HKEY_CURRENT_USER: 93 | var hkey: HKEY 94 | if RegOpenCurrentUser(0, &hkey) == ERROR_SUCCESS: 95 | phkey = hkey 96 | phkeyNeedClose = true 97 | 98 | let subkey = if parts.len >= 2: parts[1] else: "" 99 | 100 | case right: 101 | of rrRead, rrDelete: 102 | if RegOpenKeyEx(phkey, subkey, 0, sam, &result) != ERROR_SUCCESS: 103 | return 0 104 | 105 | of rrWrite: 106 | if RegCreateKeyEx(phkey, subkey, 0, nil, 0, sam, nil, &result, nil) != ERROR_SUCCESS: 107 | return 0 108 | 109 | proc regClose(hkey: HKEY) {.inline.} = 110 | RegCloseKey(hkey) 111 | 112 | proc regRead*(key: string, value: string): RegData = 113 | ## Reads a value from the registry. 114 | 115 | runnableExamples: 116 | proc example() = 117 | echo regRead(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion", "ProgramFilesDir") 118 | 119 | block okay: 120 | let hkey = regOpen(key, rrRead) 121 | if hkey == 0: break okay 122 | defer: regClose(hkey) 123 | 124 | var size, kind: DWORD 125 | if RegQueryValueEx(hkey, value, nil, &kind, nil, &size) != ERROR_SUCCESS: 126 | break okay 127 | 128 | var buffer = newString(size) 129 | if RegQueryValueEx(hkey, value, nil, &kind, cast[LPBYTE](&buffer), &size) != ERROR_SUCCESS: 130 | break okay 131 | 132 | case kind 133 | of REG_DWORD: 134 | return RegData(kind: rkRegDword, dword: cast[ptr DWORD](&buffer)[]) 135 | 136 | of REG_DWORD_BIG_ENDIAN: 137 | swapEndian32(&buffer, &buffer) 138 | return RegData(kind: rkRegDwordBigEndian, dword: cast[ptr DWORD](&buffer)[]) 139 | 140 | of REG_QWORD: 141 | return RegData(kind: rkRegQword, qword: cast[ptr QWORD](&buffer)[]) 142 | 143 | of REG_SZ, REG_EXPAND_SZ: 144 | return RegData(kind: RegKind kind, data: ($cast[TString](buffer)).nullTerminated) 145 | 146 | of REG_MULTI_SZ: 147 | return RegData(kind: rkRegMultiSz, data: ($cast[TString](buffer)). 148 | strip(leading=false, trailing=true, chars={'\0'})) 149 | 150 | else: 151 | return RegData(kind: RegKind kind, data: buffer) 152 | 153 | return RegData(kind: rkRegError) 154 | 155 | proc regWrite*(key: string): bool {.discardable.} = 156 | ## Creates a key in the registry. 157 | 158 | runnableExamples: 159 | proc example() = 160 | regWrite(r"HKEY_CURRENT_USER\Software\wAuto") 161 | 162 | let hkey = regOpen(key, rrWrite) 163 | result = (hkey != 0) 164 | regClose(hkey) 165 | 166 | proc regWrite*(key: string, name: string, value: RegData): bool {.discardable.} = 167 | ## Creates a value in the registry. 168 | 169 | runnableExamples: 170 | proc example() = 171 | regWrite(r"HKEY_CURRENT_USER\Software\wAuto", "Key1", RegData(kind: rkRegSz, data: "Test")) 172 | 173 | block okay: 174 | if value.kind == rkRegError: break okay 175 | 176 | let hkey = regOpen(key, rrWrite) 177 | if hkey == 0: break okay 178 | defer: regClose(hkey) 179 | 180 | var buffer: string 181 | case value.kind 182 | of rkRegDword: 183 | buffer = newString(4) 184 | cast[ptr DWORD](&buffer)[] = value.dword 185 | 186 | of rkRegDwordBigEndian: 187 | buffer = newString(4) 188 | cast[ptr DWORD](&buffer)[] = value.dword 189 | swapEndian32(&buffer, &buffer) 190 | 191 | of rkRegQword: 192 | buffer = newString(8) 193 | cast[ptr QWORD](&buffer)[] = value.qword 194 | 195 | of rkRegSz, rkRegExpandSz: 196 | buffer = string T(value.data) 197 | 198 | of rkRegMultiSz: 199 | for line in value.data.split('\0'): 200 | buffer.add string T(line) 201 | buffer.add '\0'.repeat(sizeof(TChar)) 202 | 203 | else: 204 | buffer = value.data 205 | 206 | if RegSetValueEx(hkey, name, 0, DWORD value.kind, 207 | cast[LPBYTE](&buffer), DWORD buffer.len) != ERROR_SUCCESS: 208 | break okay 209 | 210 | return true 211 | 212 | proc regWrite*(key: string, name: string, value: string): bool {.discardable.} = 213 | ## Creates a value of REG_SZ type in the registry. 214 | 215 | runnableExamples: 216 | proc example() = 217 | regWrite(r"HKEY_CURRENT_USER\Software\wAuto", "Key2", "Test") 218 | 219 | regWrite(key, name, RegData(kind: rkRegSz, data: value)) 220 | 221 | proc regWrite*(key: string, name: string, value: DWORD): bool {.discardable.} = 222 | ## Creates a value of REG_DWORD type in the registry. 223 | 224 | runnableExamples: 225 | proc example() = 226 | regWrite(r"HKEY_CURRENT_USER\Software\wAuto", "Key3", 12345) 227 | 228 | regWrite(key, name, RegData(kind: rkRegDword, dword: value)) 229 | 230 | proc regDelete*(key: string, name: string): bool {.discardable.} = 231 | ## Deletes a value from the registry. 232 | 233 | runnableExamples: 234 | proc example() = 235 | regDelete(r"HKEY_CURRENT_USER\Software\wAuto", "Key3") 236 | 237 | let hkey = regOpen(key, rrDelete) 238 | if hkey != 0: 239 | if RegDeleteValue(hkey, name) == ERROR_SUCCESS: 240 | result = true 241 | regClose(hkey) 242 | 243 | proc regDelete*(key: string): bool {.discardable.} = 244 | ## Deletes the entire key from the registry. 245 | ## **Deleting from the registry is potentially dangerous--please exercise caution!** 246 | 247 | runnableExamples: 248 | proc example() = 249 | regDelete(r"HKEY_CURRENT_USER\Software\wAuto") 250 | 251 | let hkey = regOpen(key, rrDelete) 252 | if hkey != 0: 253 | if SHDeleteKey(hkey, "") == ERROR_SUCCESS : 254 | result = true 255 | regClose(hkey) 256 | 257 | iterator regKeys*(key: string): string = 258 | ## Iterates over subkeys. 259 | let hkey = regOpen(key, rrRead) 260 | if hkey != 0: 261 | var 262 | i: DWORD = 0 263 | buffer = T(255) 264 | 265 | while true: 266 | if RegEnumKey(hkey, i, &buffer, 255) != ERROR_SUCCESS: break 267 | yield $buffer.nullTerminated 268 | i.inc 269 | 270 | regClose(hkey) 271 | 272 | iterator regValues*(key: string): tuple[name: string, kind: RegKind] = 273 | ## Iterates over name and kind of values. 274 | let hkey = regOpen(key, rrRead) 275 | if hkey != 0: 276 | var 277 | buffer = T(32767) 278 | size, kind, i: DWORD = 0 279 | 280 | while true: 281 | size = DWORD buffer.len 282 | if RegEnumValue(hkey, i, &buffer, &size, nil, &kind, nil, nil) != ERROR_SUCCESS: break 283 | yield ($buffer.nullTerminated, RegKind kind) 284 | i.inc 285 | 286 | regClose(hkey) 287 | -------------------------------------------------------------------------------- /wAuto/window.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | ## This module contains support to manipulate windows and standard windows controls. 9 | ## There are two ways to find a specified window. windows() iterator, 10 | ## or enumerate() template. The main difference is that inside the iterator, 11 | ## it must create a seq of all windows and then yield one by one. However, the 12 | ## enumerate() template can be break during enumeration if specified window is 13 | ## found. Moreover, the enumerate() template can aslo collect the window 14 | ## that match the specified condition. 15 | 16 | import strutils 17 | import winim/lean, winim/inc/commctrl 18 | import wNim/[wApp, wMacros] 19 | import common, private/utils 20 | 21 | export common, wApp.wDefault, wApp.wDefaultPoint 22 | 23 | type 24 | EnumWindowCallback = proc (hwnd: HWND): bool 25 | EnumerateBreakError = object of CatchableError 26 | 27 | EnumData = object 28 | callback: EnumWindowCallback 29 | isBreak: bool 30 | 31 | EnumTextData = object 32 | detectHidden: bool 33 | text: string 34 | 35 | proc getClassName*(window: Window): string 36 | proc getTitle*(window: Window): string 37 | 38 | proc `$`*(x: Window): string {.borrow.} 39 | ## The stringify operator for a window. 40 | 41 | proc `==`*(x, y: Window): bool {.borrow.} 42 | ## Checks for equality between two window. 43 | 44 | proc repr*(x: Window): string = 45 | ## Returns string representation of a window. 46 | result = "Window(ClassName: " 47 | result.add x.getClassName.escape 48 | result.add ", Title: " 49 | result.add x.getTitle.escape 50 | result.add ")" 51 | 52 | proc getMouseMessage(mb: MouseButton): (int, int, int) = 53 | let mb = case mb 54 | of mbPrimary: 55 | if GetSystemMetrics(SM_SWAPBUTTON) == 0: mbLeft else: mbRight 56 | of mbSecondary: 57 | if GetSystemMetrics(SM_SWAPBUTTON) == 0: mbRight else: mbLeft 58 | else: mb 59 | 60 | result = case mb 61 | of mbRight: (WM_RBUTTONDOWN, WM_RBUTTONUP, MK_RBUTTON) 62 | of mbMiddle: (WM_MBUTTONDOWN, WM_MBUTTONUP, MK_MBUTTON) 63 | else: (WM_LBUTTONDOWN, WM_LBUTTONUP, MK_LBUTTON) 64 | 65 | proc enumChildrenProc(hwnd: HWND, data: LPARAM): WINBOOL {.stdcall.} = 66 | let pData = cast[ptr EnumData](data) 67 | pData[].isBreak = pData[].callback(hwnd) 68 | return not pData[].isBreak 69 | 70 | proc enumDescendantsProc(hwnd: HWND, data: LPARAM): WINBOOL {.stdcall.} = 71 | let pData = cast[ptr EnumData](data) 72 | pData[].isBreak = pData[].callback(hwnd) 73 | if not pData[].isBreak: 74 | EnumChildWindows(hwnd, enumDescendantsProc, data) 75 | 76 | return not pData[].isBreak 77 | 78 | proc enumChildrenTextProc(hwnd: HWND, data: LPARAM): WINBOOL {.stdcall.} = 79 | let pData = cast[ptr EnumTextData](data) 80 | defer: 81 | result = true # the callback return true to continue enumeration 82 | 83 | if IsWindowVisible(hwnd) == FALSE and not pData[].detectHidden: 84 | return 85 | 86 | var length: LRESULT 87 | if SendMessageTimeout(hwnd, WM_GETTEXTLENGTH, 0, 0, 88 | SMTO_ABORTIFHUNG, 100, cast[PDWORD_PTR](&length)) == 0: return 89 | 90 | var buffer = T(length + 8) 91 | var ret: int 92 | 93 | if SendMessageTimeout(hwnd, WM_GETTEXT, WPARAM buffer.len, cast[LPARAM](&buffer), 94 | SMTO_ABORTIFHUNG, 100, cast[PDWORD_PTR](&ret)) == 0: return 95 | 96 | buffer.setLen(ret) 97 | pData[].text.add($buffer & "\n") 98 | 99 | proc enumChildren(callback: EnumWindowCallback) = 100 | # Enumerates all top-level windows. 101 | # The callback return true to stop enumeration. 102 | var data = EnumData(callback: callback) 103 | EnumWindows(enumChildrenProc, cast[LPARAM](addr data)) 104 | 105 | proc enumChildren(hwnd: HWND, callback: EnumWindowCallback) = 106 | # Enumerates the children that belong to the specified parent window. 107 | # The callback return true to stop enumeration. 108 | var data = EnumData(callback: callback) 109 | EnumChildWindows(hwnd, enumChildrenProc, cast[LPARAM](addr data)) 110 | 111 | proc enumDescendants(callback: EnumWindowCallback) = 112 | # Enumerates all top-level windows and their descendants. 113 | # The callback return true to stop enumeration. 114 | var data = EnumData(callback: callback) 115 | EnumWindows(enumDescendantsProc, cast[LPARAM](addr data)) 116 | 117 | proc enumDescendants(hwnd: HWND, callback: EnumWindowCallback) = 118 | # Enumerates all the descendants that belong to the specified window. 119 | # The callback return true to stop enumeration. 120 | var data = EnumData(callback: callback) 121 | EnumChildWindows(hwnd, enumDescendantsProc, cast[LPARAM](addr data)) 122 | 123 | proc getHandle*(window: Window): HWND {.property, inline.} = 124 | ## Gets the Win32 hWnd from the specified window. 125 | result = HWND window 126 | 127 | proc getTitle*(window: Window): string {.property.} = 128 | ## Retrieves the full title from a window. 129 | var title = T(65536) 130 | title.setLen(GetWindowText(HWND window, &title, 65536)) 131 | result = $title 132 | 133 | proc setTitle*(window: Window, title: string) {.property.} = 134 | ## Changes the title of a window. 135 | SetWindowText(HWND window, title) 136 | 137 | proc getClassName*(window: Window): string {.property.} = 138 | ## Retrieves the class name from a window. 139 | var class = T(256) 140 | class.setLen(GetClassName(HWND window, &class, 256)) 141 | result = $class 142 | 143 | proc getText*(window: Window, detectHidden = false): string {.property.} = 144 | ## Retrieves the text from a window. 145 | var data = EnumTextData(detectHidden: detectHidden) 146 | discard enumChildrenTextProc(HWND window, cast[LPARAM](addr data)) 147 | EnumChildWindows(HWND window, enumChildrenTextProc, cast[LPARAM](addr data)) 148 | result = data.text 149 | 150 | proc getStatusBarText*(window: Window, index = 0): string {.property.} = 151 | ## Retrieves the text from a standard status bar control. 152 | var pResult = addr result 153 | 154 | proc doGetText(hStatus: HWND) = 155 | var count, length: int 156 | if SendMessageTimeout(hStatus, SB_GETPARTS, 0, 0, 157 | SMTO_ABORTIFHUNG, 100, cast[PDWORD_PTR](&count)) == 0: return 158 | 159 | if SendMessageTimeout(hStatus, SB_GETTEXTLENGTH, WPARAM index, 0, 160 | SMTO_ABORTIFHUNG, 100, cast[PDWORD_PTR](&length)) == 0: return 161 | 162 | length = length and 0xffff 163 | if length == 0: return 164 | 165 | let bufferSize = length * sizeof(TChar) + 8 166 | var rp = remoteAlloc(HWND window, bufferSize) 167 | if not rp.ok: return 168 | defer: rp.remoteDealloc() 169 | 170 | if SendMessageTimeout(hStatus, SB_GETTEXT, WPARAM index, cast[LPARAM](rp.address), 171 | SMTO_ABORTIFHUNG, 100, cast[PDWORD_PTR](&length)) == 0: return 172 | 173 | length = length and 0xffff 174 | if length == 0: return 175 | 176 | var buffer = rp.remoteRead() 177 | pResult[] = nullTerminated($cast[TString](buffer)) 178 | 179 | if window.getClassName == "msctls_statusbar32": 180 | doGetText(HWND window) 181 | 182 | else: 183 | enumDescendants(HWND window) do (hStatus: HWND) -> bool: 184 | if getClassName(Window hStatus) == "msctls_statusbar32": 185 | doGetText(hStatus) 186 | return true 187 | 188 | proc getPosition*(window: Window, pos: wPoint = (0, 0)): wPoint {.property.} = 189 | ## Retrieves the screen coordinates of specified window position. 190 | var rect: RECT 191 | if GetWindowRect(HWND window, &rect): 192 | result = (pos.x + int rect.left, pos.y + int rect.top) 193 | 194 | proc getPosition*(window: Window, x: int, y: int): wPoint {.property.} = 195 | ## Retrieves the screen coordinates of specified window position. 196 | 197 | runnableExamples: 198 | import mouse 199 | 200 | proc example() = 201 | move(activeWindow().position(100, 100)) 202 | 203 | result = getPosition(window, (x, y)) 204 | 205 | proc setPosition*(window: Window, pos: wPoint) {.property.} = 206 | ## Moves a window. 207 | SetWindowPos(HWND window, 0, cint pos.x, cint pos.y, 0, 0, 208 | SWP_NOZORDER or SWP_NOREPOSITION or SWP_NOACTIVATE or SWP_NOSIZE) 209 | 210 | proc setPosition*(window: Window, x: int, y: int) {.property, inline.} = 211 | ## Moves a window. 212 | setPosition(window, (x, y)) 213 | 214 | proc getClientPosition*(window: Window, pos: wPoint = (0, 0)): wPoint {.property.} = 215 | ## Retrieves the screen coordinates of specified client-area coordinates. 216 | var pt = POINT(x: LONG pos.x, y: LONG pos.y) 217 | ClientToScreen(HWND window, &pt) 218 | result = (int pt.x, int pt.y) 219 | 220 | proc getClientPosition*(window: Window, x: int, y: int): wPoint {.property.} = 221 | ## Retrieves the screen coordinates of specified client-area coordinates. 222 | 223 | runnableExamples: 224 | import mouse 225 | 226 | proc example() = 227 | move(activeWindow().clientPosition(100, 100)) 228 | 229 | result = getClientPosition(window, (x, y)) 230 | 231 | proc setSize*(window: Window, size: wSize) {.property.} = 232 | ## Resizes a window. 233 | SetWindowPos(HWND window, 0, 0, 0, cint size.width, cint size.height, 234 | SWP_NOZORDER or SWP_NOREPOSITION or SWP_NOACTIVATE or SWP_NOMOVE) 235 | 236 | proc setSize*(window: Window, width: int, height: int) {.property, inline.} = 237 | ## Resizes a window. 238 | setSize(window, (width, height)) 239 | 240 | proc getSize*(window: Window): wSize {.property.} = 241 | ## Retrieves the size of a given window. 242 | var rect: RECT 243 | if GetWindowRect(HWND window, &rect): 244 | result = (int(rect.right - rect.left), int(rect.bottom - rect.top)) 245 | 246 | proc getRect*(window: Window): wRect {.property.} = 247 | ## Retrieves the position and size of a given window. 248 | var rect: RECT 249 | if GetWindowRect(HWND window, &rect): 250 | result.x = int rect.left 251 | result.y = int rect.top 252 | result.width = int(rect.right - rect.left) 253 | result.height = int(rect.bottom - rect.top) 254 | 255 | proc setRect*(window: Window, rect: wRect) {.property.} = 256 | ## Moves and resizes a window. 257 | SetWindowPos(HWND window, 0, cint rect.x, cint rect.y, cint rect.width, cint rect.height, 258 | SWP_NOZORDER or SWP_NOREPOSITION or SWP_NOACTIVATE) 259 | 260 | proc setRect*(window: Window, x: int, y: int, width: int, height: int) {.property, inline.} = 261 | ## Moves and resizes a window. 262 | setRect(window, (x, y, width, height)) 263 | 264 | proc getCaretPos*(window: Window): wPoint {.property.} = 265 | ## Returns the coordinates of the caret in the given window (works for foreground window only). 266 | if window.HWND != GetForegroundWindow(): 267 | return (-1, -1) 268 | 269 | let (tid, pid) = (GetCurrentThreadId(), GetWindowThreadProcessId(HWND window, nil)) 270 | AttachThreadInput(tid, pid, TRUE) 271 | defer: AttachThreadInput(tid, pid, FALSE) 272 | 273 | var p: POINT 274 | GetCaretPos(&p) 275 | result = (int p.x, int p.y) 276 | 277 | proc getClientSize*(window: Window): wSize {.property.} = 278 | ## Retrieves the size of a given window's client area. 279 | var rect: RECT 280 | GetClientRect(HWND window, &rect) 281 | result = (int rect.right, int rect.bottom) 282 | 283 | proc getProcess*(window: Window): Process {.property.} = 284 | ## Retrieves the process associated with a window. 285 | var pid: DWORD 286 | GetWindowThreadProcessId(HWND window, &pid) 287 | result = Process pid 288 | 289 | proc getParent*(window: Window): Window {.property.} = 290 | ## Retrieves the parent of a given window. 291 | result = Window GetAncestor(HWND window, GA_PARENT) 292 | 293 | proc getChildren*(window: Window): seq[Window] {.property.} = 294 | ## Retrieves the children of a given window. 295 | let pResult = addr result 296 | enumChildren(HWND window) do (hwnd: HWND) -> bool: 297 | pResult[].add(Window hwnd) 298 | 299 | proc getActiveWindow*(): Window {.property, inline.} = 300 | ## Get the currently active window. 301 | result = Window GetForegroundWindow() 302 | 303 | proc setTransparent*(window: Window, alpha: range[0..255]) {.property.} = 304 | ## Sets the transparency of a window. 305 | ## A value of 0 sets the window to be fully transparent. 306 | let hwnd = HWND window 307 | SetWindowLongPtr(hwnd, GWL_EXSTYLE, 308 | WS_EX_LAYERED or GetWindowLongPtr(hwnd, GWL_EXSTYLE)) 309 | SetLayeredWindowAttributes(hwnd, 0, BYTE alpha, LWA_ALPHA) 310 | 311 | proc getTransparent*(window: Window): int {.property.} = 312 | ## Gets the transparency of a window. Return -1 if failed. 313 | var alpha: byte 314 | if GetLayeredWindowAttributes(HWND window, nil, &alpha, nil) == 0: return -1 315 | result = int alpha 316 | 317 | proc setOnTop*(window: Window, flag = true) {.property.} = 318 | ## Change a window's "Always On Top" attribute. 319 | if flag: 320 | SetWindowPos(HWND window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_NOACTIVATE) 321 | else: 322 | SetWindowPos(HWND window, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_NOACTIVATE) 323 | 324 | proc isExists*(window: Window): bool {.inline.} = 325 | ## Checks to see if a specified window exists. 326 | result = bool IsWindow(HWND window) 327 | 328 | proc isVisible*(window: Window): bool {.inline.} = 329 | ## Checks to see if a specified window is currently visible. 330 | result = bool IsWindowVisible(HWND window) 331 | 332 | proc isEnabled*(window: Window): bool {.inline.} = 333 | ## Checks to see if a specified window is currently enabled. 334 | result = bool IsWindowEnabled(HWND window) 335 | 336 | proc isActive*(window: Window): bool {.inline.} = 337 | ## Checks to see if a specified window is currently active. 338 | result = GetForegroundWindow() == HWND window 339 | 340 | proc isMinimized*(window: Window): bool {.inline.} = 341 | ## Checks to see if a specified window is currently minimized. 342 | result = bool IsIconic(HWND window) 343 | 344 | proc isMaximized*(window: Window): bool {.inline.} = 345 | ## Checks to see if a specified window is currently maximized. 346 | result = bool IsZoomed(HWND window) 347 | 348 | proc isFocused*(window: Window): bool = 349 | ## Checks to see if a specified window has the focus. 350 | let (tid, pid) = (GetCurrentThreadId(), GetWindowThreadProcessId(HWND window, nil)) 351 | AttachThreadInput(tid, pid, TRUE) 352 | defer: AttachThreadInput(tid, pid, FALSE) 353 | 354 | result = (window.HWND == GetFocus()) 355 | 356 | proc activate*(window: Window) = 357 | ## Activates (gives focus to) a window. 358 | let hwnd = HWND window 359 | if IsIconic(hwnd): ShowWindow(hwnd, SW_RESTORE) 360 | SetForegroundWindow(hwnd) 361 | sleep(opt("windelay")) 362 | 363 | proc close*(window: Window) = 364 | ## Closes a window. 365 | PostMessage(HWND window, WM_CLOSE, 0, 0) 366 | sleep(opt("windelay")) 367 | 368 | proc kill*(window: Window, byProcess = true) = 369 | ## Forces a window to close by terminating the related process or thread. 370 | setPrivilege("SeDebugPrivilege") 371 | var pid: DWORD 372 | var tid = GetWindowThreadProcessId(HWND window, addr pid) 373 | 374 | if byProcess: 375 | let process = OpenProcess(PROCESS_TERMINATE, 0, pid) 376 | if process != 0: 377 | TerminateProcess(process, 0) 378 | CloseHandle(process) 379 | else: 380 | let thread = OpenThread(THREAD_TERMINATE, 0, tid) 381 | if thread != 0: 382 | TerminateThread(thread, 0) 383 | CloseHandle(thread) 384 | 385 | proc show*(window: Window) {.inline.} = 386 | ## Shows window. 387 | ShowWindow(HWND window, SW_SHOW) 388 | 389 | proc hide*(window: Window) {.inline.} = 390 | ## Hides window. 391 | ShowWindow(HWND window, SW_HIDE) 392 | 393 | proc enable*(window: Window) {.inline.} = 394 | ## Enables the window. 395 | EnableWindow(HWND window, TRUE) 396 | 397 | proc disable*(window: Window) {.inline.} = 398 | ## Disables the window. 399 | EnableWindow(HWND window, FALSE) 400 | 401 | proc minimize*(window: Window) {.inline.} = 402 | ## Minimize the window. 403 | ShowWindow(HWND window, SW_MINIMIZE) 404 | 405 | proc maximize*(window: Window) {.inline.} = 406 | ## Maximize the window. 407 | ShowWindow(HWND window, SW_MAXIMIZE) 408 | 409 | proc restore*(window: Window) {.inline.} = 410 | ## Undoes a window minimization or maximization 411 | ShowWindow(HWND window, SW_RESTORE) 412 | 413 | proc minimizeAll*() {.inline.} = 414 | ## Minimizes all windows. Equal to send("#m"). 415 | PostMessage(FindWindow(("Shell_TrayWnd"), NULL), WM_COMMAND, 419, 0) 416 | sleep(opt("windelay")) 417 | 418 | proc minimizeAllUndo*() {.inline.} = 419 | ## Undoes a previous minimizeAll(). Equal to send("#+m"). 420 | PostMessage(FindWindow(("Shell_TrayWnd"), NULL), WM_COMMAND, 416, 0) 421 | sleep(opt("windelay")) 422 | 423 | proc focus*(window: Window) = 424 | ## Focus a window. 425 | let (tid, pid) = (GetCurrentThreadId(), GetWindowThreadProcessId(HWND window, nil)) 426 | AttachThreadInput(tid, pid, TRUE) 427 | defer: AttachThreadInput(tid, pid, FALSE) 428 | SetFocus(HWND window) 429 | sleep(opt("windelay")) 430 | 431 | proc click*(window: Window, button = mbLeft, pos = wDefaultPoint, clicks = 1) = 432 | ## Sends a mouse click command to a given window. The default position is center. 433 | let 434 | (msgDown, msgUp, wParam) = getMouseMessage(button) 435 | size = window.getSize 436 | lParam = MAKELPARAM( 437 | if pos.x == wDefault: (size.width div 2) else: pos.x, 438 | if pos.y == wDefault: (size.height div 2) else: pos.y) 439 | 440 | for i in 1..clicks: 441 | PostMessage(HWND window, UINT msgDown, WPARAM wParam, lparam) 442 | sleep(opt("mouseclickdowndelay")) 443 | PostMessage(HWND window, UINT msgUp, WPARAM wParam, lparam) 444 | sleep(opt("mouseclickdelay")) 445 | 446 | proc click*(window: Window, item: MenuItem) = 447 | ## Invokes a menu item of a window. 448 | if item.byPos: 449 | PostMessage(HWND window, WM_MENUCOMMAND, WPARAM item.index, LPARAM item.handle) 450 | else: 451 | PostMessage(HWND window, WM_COMMAND, WPARAM item.id, 0) 452 | sleep(opt("windelay")) 453 | 454 | proc flash*(window: Window, flashes = 4, delay = 500, wait = true) = 455 | ## Flashes a window in the taskbar. 456 | let hwnd = HWND window 457 | if wait: 458 | for i in 1..(flashes - 2) * 2 + 1: 459 | FlashWindow(hwnd, true) 460 | sleep(delay) 461 | else: 462 | var fi = FLASHWINFO( 463 | cbSize: cint sizeof(FLASHWINFO), 464 | hwnd: hwnd, 465 | dwFlags: FLASHW_ALL or FLASHW_TIMERNOFG, 466 | uCount: UINT flashes, 467 | dwTimeout: DWORD delay) 468 | 469 | FlashWindowEx(&fi) 470 | sleep(opt("windelay")) 471 | 472 | iterator windows*(): Window = 473 | ## Iterates over all the top-level windows. 474 | var list = newSeq[Window]() 475 | enumChildren do (hwnd: HWND) -> bool: 476 | list.add(Window hwnd) 477 | 478 | for window in list: yield window 479 | 480 | iterator windows*(parent: Window): Window = 481 | ## Iterates over the children that belong to the specified parent window. 482 | var list = newSeq[Window]() 483 | enumChildren(HWND parent) do (hwnd: HWND) -> bool: 484 | list.add(Window hwnd) 485 | 486 | for window in list: yield window 487 | 488 | iterator allWindows*(): Window = 489 | ## Iterates over all top-level windows and their descendants. 490 | var list = newSeq[Window]() 491 | enumDescendants do (hwnd: HWND) -> bool: 492 | list.add(Window hwnd) 493 | 494 | for window in list: yield window 495 | 496 | iterator allWindows*(parent: Window): Window = 497 | ## Iterates over all the descendants that belong to the specified window. 498 | var list = newSeq[Window]() 499 | enumDescendants(HWND parent) do (hwnd: HWND) -> bool: 500 | list.add(Window hwnd) 501 | 502 | for window in list: yield window 503 | 504 | proc walkMenu(list: var seq[MenuItem], hMenu: HMENU) = 505 | var menuInfo = MENUINFO(cbSize: cint sizeof(MENUINFO), fMask: MIM_STYLE) 506 | GetMenuInfo(hMenu, &menuInfo) 507 | var byPos = (menuInfo.dwStyle and MNS_NOTIFYBYPOS) != 0 508 | 509 | for i in 0.. bool: 568 | window = Window hwnd 569 | try: 570 | when compiles(bool body): 571 | if body: 572 | result.add window 573 | else: 574 | body 575 | except EnumerateBreakError: 576 | return true 577 | 578 | discardable result 579 | 580 | template enumerate*(parent: untyped, body: untyped): untyped = 581 | ## Enumerates the children that belong to the specified parent window. 582 | ## A *window* symbol is injected into the body. 583 | ## The template can return a seq[Window] that collects all *window* if the body can be evaluated as true. 584 | ## Use *enumerateBreak* template to break the enumeration. 585 | 586 | runnableExamples: 587 | proc example() = 588 | enumerate: 589 | if window.className == "Notepad": 590 | enumerate(window): 591 | echo window.className 592 | 593 | enumerateBreak 594 | 595 | var hParent = HWND parent # allow nested enumerate 596 | block: 597 | template enumerateBreak {.used.} = raise newException(EnumerateBreakError, "") 598 | 599 | var 600 | window {.inject.}: Window 601 | result = newSeq[Window]() 602 | 603 | enumChildren(hParent) do (hwnd: HWND) -> bool: 604 | window = Window hwnd 605 | try: 606 | when compiles(bool body): 607 | if body: 608 | result.add window 609 | else: 610 | body 611 | except EnumerateBreakError: 612 | return true 613 | 614 | discardable result 615 | 616 | template enumerateAll*(body: untyped): untyped = 617 | ## Enumerates all top-level windows and their descendants. 618 | ## A *window* symbol is injected into the body. 619 | ## The template can return a seq[Window] that collects all *window* if the body can be evaluated as true. 620 | ## Use *enumerateBreak* template to break the enumeration. 621 | 622 | runnableExamples: 623 | proc example() = 624 | enumerateAll: 625 | if window.className == "Button" and window.title != "": 626 | echo window.repr 627 | 628 | block: 629 | template enumerateBreak {.used.} = raise newException(EnumerateBreakError, "") 630 | 631 | var 632 | window {.inject.}: Window 633 | result = newSeq[Window]() 634 | 635 | enumDescendants do (hwnd: HWND) -> bool: 636 | window = Window hwnd 637 | try: 638 | when compiles(bool body): 639 | if body: 640 | result.add window 641 | else: 642 | body 643 | except EnumerateBreakError: 644 | return true 645 | 646 | discardable result 647 | 648 | template enumerateAll*(parent: untyped, body: untyped): untyped = 649 | ## Enumerates all the descendants that belong to the specified window. 650 | ## A *window* symbol is injected into the body. 651 | ## The template can return a seq[Window] that collects all *window* if the body can be evaluated as true. 652 | ## Use *enumerateBreak* template to break the enumeration. 653 | 654 | runnableExamples: 655 | proc example() = 656 | enumerate: 657 | if window.className == "Notepad": 658 | enumerateAll(window): 659 | echo window.className 660 | 661 | enumerateBreak 662 | 663 | var hParent = HWND parent # allow nested enumerate 664 | block: 665 | template enumerateBreak {.used.} = raise newException(EnumerateBreakError, "") 666 | 667 | var 668 | window {.inject.}: Window 669 | result = newSeq[Window]() 670 | 671 | enumDescendants(hParent) do (hwnd: HWND) -> bool: 672 | window = Window hwnd 673 | try: 674 | when compiles(bool body): 675 | if body: 676 | result.add window 677 | else: 678 | body 679 | except EnumerateBreakError: 680 | return true 681 | 682 | discardable result 683 | 684 | template waitAny*(condition: untyped, timeout: untyped = 0): untyped = 685 | ## Repeatly examines all the top-level windows until condition becomes true for any window. 686 | ## A *window* symbol is injected into the condition. 687 | ## *timeout* specifies how long to wait (in seconds). Default (0) is to wait indefinitely. 688 | ## The template can return the *window* that match the condition. 689 | 690 | runnableExamples: 691 | proc example() = 692 | waitAny(window.className == "Notepad" and window.isActive) 693 | 694 | block: 695 | var 696 | timer = GetTickCount() 697 | window {.inject.}: Window 698 | found = false 699 | 700 | while not found: 701 | enumChildren do (hwnd: HWND) -> bool: 702 | window = Window hwnd 703 | if condition: 704 | found = true 705 | return true 706 | 707 | sleep(opt("winwaitdelay")) 708 | if timeout != 0 and (GetTickCount() -% timer) > timeout * 1000: 709 | window = Window 0 710 | break 711 | 712 | discardable window 713 | 714 | template waitAll*(condition: untyped, timeout: untyped = 0): untyped = 715 | ## Repeatly examines all the top-level windows until condition becomes true for all windows. 716 | ## A *window* symbol is injected into the condition. 717 | ## *timeout* specifies how long to wait (in seconds). Default (0) is to wait indefinitely. 718 | 719 | runnableExamples: 720 | proc example() = 721 | waitAll(window.className != "Notepad") 722 | 723 | block: 724 | var 725 | timer = GetTickCount() 726 | window {.inject.}: Window 727 | flag: bool 728 | 729 | while not flag: 730 | flag = true 731 | enumChildren do (hwnd: HWND) -> bool: 732 | window = Window hwnd 733 | flag = flag and (bool condition) 734 | 735 | sleep(opt("winwaitdelay")) 736 | if timeout != 0 and (GetTickCount() -% timer) > timeout * 1000: 737 | break 738 | -------------------------------------------------------------------------------- /wauto.nimble: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # wAuto - Windows Automation Module 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | # Package 9 | version = "1.3.0" 10 | author = "Ward" 11 | description = "wAuto - Windows Automation Module" 12 | license = "MIT" 13 | skipDirs = @["docs"] 14 | 15 | # Dependencies 16 | requires "nim >= 1.0.0", "winim >= 3.3.3", "wNim >= 0.11.0", "npeg >= 0.22.2" 17 | --------------------------------------------------------------------------------