au3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,152 @@
1
+ #Encoding: UTF-8
2
+ #This file is part of au3.
3
+ #Copyright © 2009 Marvin Gülker
4
+ #
5
+ #au3 is published under the same terms as Ruby.
6
+ #See http://www.ruby-lang.org/en/LICENSE.txt
7
+
8
+ module AutoItX3
9
+
10
+ #Lowest process priorety
11
+ IDLE_PRIORITY = 0
12
+ #Subnormal process priority
13
+ SUBNORMAL_PRIORITY = 1
14
+ #Normal process priority
15
+ NORMAL_PRIORITY = 2
16
+ #Process priority above normal
17
+ SUPNORMAL_PRIORITY = 3
18
+ #High process priority
19
+ HIGH_PRIORITY = 4
20
+ #Highest process priority. Use this with caution, it's is the priority system processes run with.
21
+ REALTIME_PRIORITY = 5
22
+
23
+ #Logs the currect user out
24
+ LOGOFF = 0
25
+ #Shuts the computer down
26
+ SHUTDOWN = 1
27
+ #Reboot the computer
28
+ REBOOT = 2
29
+ #Force hanging applications to close
30
+ FORCE_CLOSE = 4
31
+ #Turn the power off after shutting down (if the computer supports this)
32
+ POWER_DOWN = 8
33
+
34
+ class << self
35
+
36
+ #call-seq:
37
+ # close_process(pid) ==> nil
38
+ # kill_process(pid) ==> nil
39
+ #Closes the given process.
40
+ def close_process(pid)
41
+ @functions[__method__] ||= AU3_Function.new("ProcessClose", 'S', 'L')
42
+ @functions[__method__].call(pid.to_s.wide)
43
+ nil
44
+ end
45
+ alias kill_process close_process
46
+
47
+ #Checks wheather or not the given name or PID exists. If successful,
48
+ #this method returns the PID of the process.
49
+ def process_exists?(pid)
50
+ @functions[__method__] ||= AU3_Function.new("ProcessExists", 'S', 'L')
51
+ pid = @functions[__method__].call(pid.to_s.wide)
52
+ if pid == 0
53
+ false
54
+ else
55
+ pid
56
+ end
57
+
58
+ end
59
+
60
+ #Sets a process's priority. Use one of the *_PRIORITY constants.
61
+ def set_process_priority(pid, priority)
62
+ @functions[__method__] ||= AU3_Function.new("ProcessSetPriority", 'SL', 'L')
63
+ @functions[__method__].call(pid.to_s.wide, priority)
64
+
65
+ case last_error
66
+ when 1 then raise(Au3Error, "Unknown error occured when trying to set process priority of '#{pid}'!")
67
+ when 2 then raise(Au3Error, "Unsupported priority '#{priority}'!")
68
+ else
69
+ return priority
70
+ end
71
+ end
72
+
73
+ #Waits for the given process name to exist. This is the only process-related
74
+ #method that doesn't take a PID, because to wait for a special PID doesn't make
75
+ #sense, since PIDs are generated randomly.
76
+ #
77
+ #Return false if +timeout+ was reached.
78
+ def wait_for_process(procname, timeout = 0)
79
+ @functions[__method__] ||= AU3_Function.new("ProcessWait", 'SL', 'L')
80
+ if @functions[__method__].call(procname.to_s.wide, timeout) == 0
81
+ false
82
+ else
83
+ true
84
+ end
85
+ end
86
+
87
+ #Waits for the given process name or PID to disappear.
88
+ #
89
+ #Returns false if +timeout+ was reached.
90
+ def wait_for_process_close(pid, timeout = 0)
91
+ @functions[__method__] ||= AU3_Function.new("ProcessWaitClose", 'SL', 'L')
92
+ if @functions[__method__].call(pid.to_s.wide, timeout) == 0
93
+ false
94
+ else
95
+ true
96
+ end
97
+ end
98
+
99
+ #Runs a program. The program flow continues, if you want to wait for the process to
100
+ #finish, use #run_and_wait.
101
+ #Returns the PID of the created process or nil if there was a failure starting the process.
102
+ #The +flag+ parameter can be one of the SW_HIDE, SW_MINIMZE or SW_MAXIMIZE
103
+ #constants in the Window class.
104
+ def run(name, workingdir = "", flag = 1)
105
+ @functions[__method__] ||= AU3_Function.new("Run", 'SSL', 'L')
106
+ pid = @functions[__method__].call(name.wide, workingdir.wide, flag)
107
+ raise(Au3Error, "An error occured while starting process '#{name}'!") if last_error == 1
108
+ pid
109
+ end
110
+
111
+ #Runs a program. This method waits until the process has finished and returns
112
+ #the exitcode of the process (or false if there was an error initializing it). If
113
+ #you don't want this behaviour, use #run.
114
+ #The +flag+ parameter can be one of the SW_HIDE, SW_MINIMZE or SW_MAXIMIZE
115
+ #constants in the Window class.
116
+ def run_and_wait(name, workingdir = "", flag = 1)
117
+ @functions[__method__] ||= AU3_Function.new("RunWait", 'SSL', 'L')
118
+ exitcode = @functions[__method__].call(name.wide, workingdir.wide, flag)
119
+ raise(Au3Error, "An error occured while starting process '#{name}'!") if last_error == 1
120
+ exitcode
121
+ end
122
+
123
+ #Changes the the owner of following #run and #run_and_wait methods to the given
124
+ #user. Raises a NotImplementedError if your system is Win2000 or older.
125
+ def run_as_set(username, domain, password, options = 1)
126
+ @functions[__method__] ||= AU3_Function.new("RunAsSet", 'SSSI', 'L')
127
+ if @functions[__method__].call(username.wide, domain.wide, password.wide, options) == 0
128
+ raise(NotImplementedError, "Your system does not support the #run_as_set method.")
129
+ end
130
+ nil
131
+ end
132
+
133
+ #Executes one of the the following commands:
134
+ #- SHUTDOWN
135
+ #- REBOOT
136
+ #- LOGOFF
137
+ #You can combine the above actions with the below constants, except
138
+ #LOGOFF and POWER_DOWN. Use the + operator to combine them.
139
+ #- FORCE_CLOSE
140
+ #- POWER_DOWN
141
+ def shutdown(code)
142
+ @functions[__method__] ||= AU3_Function.new("Shutdown", 'L', 'L')
143
+ if @functions[__method__].call(code) == 0
144
+ false
145
+ else
146
+ true
147
+ end
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1,411 @@
1
+ #Encoding: UTF-8
2
+ #This file is part of au3.
3
+ #Copyright © 2009 Marvin Gülker
4
+ #
5
+ #au3 is published under the same terms as Ruby.
6
+ #See http://www.ruby-lang.org/en/LICENSE.txt
7
+
8
+ module AutoItX3
9
+
10
+ #A Window object holds a (pseudo) reference to a
11
+ #window that may be shown on the screen or not.
12
+ #If you want to get a real handle to the window,
13
+ #call #handle on your Window object (but you won't
14
+ #need that unless you want to use it for Win32 API calls).
15
+ class Window
16
+
17
+ #A window describing the desktop.
18
+ DESKTOP_WINDOW = "Program Manager"
19
+ #A window describing the active (foreground) window.
20
+ ACTIVE_WINDOW = ""
21
+ #Hide the window.
22
+ SW_HIDE = 0
23
+ #Show the window.
24
+ SW_SHOW = 5
25
+ #Minimize the window.
26
+ SW_MINIMIZE = 6
27
+ #Maximize the window.
28
+ SW_MAXIMIZE = 3
29
+ #Restore a minimized window.
30
+ SW_RESTORE = 9
31
+ #Uses the default SW_ value of the application.
32
+ SW_SHOWDEFAULT = 10
33
+ #Same as SW_MINIMIZE, but doesn't activate the window.
34
+ SW_SHOWMINNOACTIVE = 7
35
+ #Same as SW_SHOW, but doesn't activate the window.
36
+ SW_SHOWNA = 8
37
+
38
+ @functions = {}
39
+
40
+ class << self
41
+
42
+ def functions
43
+ @functions
44
+ end
45
+
46
+ def functions=(hsh)
47
+ @functions = hsh
48
+ end
49
+
50
+ #Checks if a window with the given properties exists.
51
+ def exists?(title, text = "")
52
+ @functions[__method__] ||= AU3_Function.new("WinExists", 'SS', 'L')
53
+ if @functions[__method__].call(title.wide, text.wide) == 0
54
+ return false;
55
+ else
56
+ return true;
57
+ end
58
+ end
59
+
60
+ #Returns a two-element array of form <tt>[x , y]</tt> reflecting the
61
+ #position of the caret in the active window. This doesn't work with
62
+ #every window.
63
+ def caret_pos
64
+ @functions[:caret_pos_x] ||= AU3_Function.new("WinGetCaretPosX", '', 'L')
65
+ @functions[:caret_pos_y] ||= AU3_Function.new("WinGetCaretPosY", '', 'L')
66
+ pos = [@functions[:caret_pos_x].call, @functions[:caret_pos_y].call]
67
+ raise(Au3Error, "Unknown error occured while retrieving caret coordinates!") if AutoItX3.last_error == 1
68
+ pos
69
+ end
70
+
71
+ #Minimizes all available windows.
72
+ def minimize_all
73
+ @functions[__method__] ||= AU3_Function.new("WinMinimizeAll", '')
74
+ @functions[__method__].call
75
+ nil
76
+ end
77
+
78
+ #Undoes a previous call to Window.minimize_all.
79
+ def undo_minimize_all
80
+ @functions[__method__] ||= AU3_Function.new("WinMinimizeAllUndo", '')
81
+ @functions[__method__].call
82
+ nil
83
+ end
84
+
85
+ #Waits for a window with the given properties to exist. You may
86
+ #specify a +timeout+ in seconds. +wait+ normally returns true, but if
87
+ #the timeout is expired, it returns false.
88
+ def wait(title, text = "", timeout = 0)
89
+ @functions[__method__] ||= AU3_Function.new("WinWait", 'SSL', 'L')
90
+ @functions[__method__].call(title.wide, text.wide, timeout) != 0
91
+ end
92
+
93
+ end
94
+
95
+ #Creates a new Window object. This method checks if a window
96
+ #with the given properties exists (via Window.exists?) and raises
97
+ #an Au3Error if it does not. Use Window::DESKTOP_WINDOW as
98
+ #the +title+ to get a window describing the desktop. Use Window::ACTIVE_WINDOW
99
+ #as the +title+ to get a window describing the active (foreground) window.
100
+ def initialize(title, text = "")
101
+ @title = title
102
+ @text = text
103
+ raise(Au3Error, "Can't get a handle to a non-existing window!") unless Window.exists?(@title, @text)
104
+ end
105
+
106
+ #Human-readable output of form <tt>"<Window: WINDOW_TITLE (WINDOW_HANDLE)>"</tt>.
107
+ #The title is determined by calling #title.
108
+ def inspect
109
+ "<Window: #{title} (#{handle})>"
110
+ end
111
+
112
+ #Returns +self+'s title by returning the value of @title.
113
+ def to_s
114
+ @title
115
+ end
116
+
117
+ #Returns the handle of the window as an integer by calling
118
+ #<tt>.to_i(16)</tt> on the result of #handle.
119
+ def to_i
120
+ handle.to_i(16)
121
+ end
122
+
123
+ #Activates the window and returns true if it was successfully activated (using #active? to check).
124
+ def activate
125
+ Window.functions[__method__] ||= AU3_Function.new("WinActivate", 'SS')
126
+ Window.functions[__method__].call(@title.wide, @text.wide)
127
+ active?
128
+ end
129
+
130
+ #Checks wheather or not the window is active.
131
+ def active?
132
+ Window.functions[__method__] ||= AU3_Function.new("WinActive", 'SS', 'L')
133
+ if Window.functions[__method__].call(@title.wide, @text.wide) == 0
134
+ return false
135
+ else
136
+ return true
137
+ end
138
+ end
139
+
140
+ #Sends WM_CLOSE to +self+. WM_CLOSE may be processed by the window,
141
+ #it could, for example, ask to save or the like. If you want to kill a window
142
+ #without giving the ability to process your message, use the #kill method.
143
+ def close
144
+ Window.functions[__method__] ||= AU3_Function.new("WinClose", 'SS', 'L')
145
+ Window.functions[__method__].call(@title.wide, @text.wide)
146
+ nil
147
+ end
148
+
149
+ #call-seq:
150
+ # exists? ==> true or false
151
+ # valid? ==> true or false
152
+ #
153
+ #Calls the Window.exists? class method with the values given in Window.new.
154
+ def exists?
155
+ Window.exists?(@title, @text)
156
+ end
157
+ alias valid? exists?
158
+
159
+ #*Returns an array of all used window classes of +self+.
160
+ def class_list
161
+ Window.functions[__method__] ||= AU3_Function.new("WinGetClassList", 'SSPI')
162
+ buffer = " " * AutoItX3::BUFFER_SIZE
163
+ buffer.wide!
164
+ Window.functions[__method__].call(@title.wide, @text.wide, buffer, AutoItX3::BUFFER_SIZE - 1)
165
+ raise_unfound if AutoItX3.last_error == 1
166
+ buffer.normal.split("\n").map{|str| str.strip.empty? ? nil : str.strip}.compact
167
+ end
168
+
169
+ #Returns the client area size of +self+ as a two-element array of
170
+ #form <tt>[ width , height ]</tt>. Returns <tt>[0, 0]</tt> on minimized
171
+ #windows.
172
+ def client_size
173
+ Window.functions[:client_size_width] ||= AU3_Function.new("WinGetClientSizeWidth", 'SS', 'L')
174
+ Window.functions[:client_size_height] ||= AU3_Function.new("WinGetClientSizeHeight", 'SS', 'L')
175
+ size = [Window.functions[:client_size_width].call, Window.functions[:client_size_height].call]
176
+ raise_unfound if AutoItX3.last_error == 1
177
+ size
178
+ end
179
+
180
+ #Returns the numeric handle of a window as a string. It can be used
181
+ #with the WinTitleMatchMode option set to advanced or for direct calls
182
+ #to the windows API (but you have to call <tt>.to_i(16)</tt> on the string then).
183
+ def handle
184
+ Window.functions[__method__] ||= AU3_Function.new("WinGetHandle", 'SSPI')
185
+ buffer = " " * AutoItX3::BUFFER_SIZE
186
+ buffer.wide!
187
+ Window.functions[__method__].call(@title.wide, @text.wide, buffer, AutoItX3::BUFFER_SIZE - 1)
188
+ raise_unfound if AutoItX3.last_error == 1
189
+ buffer.normal.strip
190
+ end
191
+
192
+ #Returns the position and size of +self+ in a four-element array
193
+ #of form <tt>[x, y, width, height]</tt>.
194
+ def rect
195
+ Window.functions[:xpos] ||= AU3_Function.new("WinGetPosX", 'SS', 'L')
196
+ Window.functions[:ypos] ||= AU3_Function.new("WinGetPosY", 'SS', 'L')
197
+ Window.functions[:width] ||= AU3_Function.new("WinGetPosWidth", 'SS', 'L')
198
+ Window.functions[:height] ||= AU3_Function.new("WinGetPosHeight", 'SS', 'L')
199
+
200
+ title = @title.wide
201
+ text = @text.wide
202
+
203
+ rect = [
204
+ Window.functions[:xpos].call(title, text),
205
+ Window.functions[:ypos].call(title, text),
206
+ Window.functions[:width].call(title, text),
207
+ Window.functions[:height].call(title, text)
208
+ ]
209
+ raise_unfound if AutoItX3.last_error == 1
210
+ rect
211
+ end
212
+
213
+ #Returns the process identification number of +self+'s window
214
+ #procedure.
215
+ def pid
216
+ Window.functions[__method__] ||= AU3_Function.new("WinGetProcess", 'SSPI', 'L')
217
+ buffer = " " * AutoItX3::BUFFER_SIZE
218
+ buffer.wide!
219
+ Window.functions[__method__].call(@title.wide, @text.wide, buffer, AutoItX3::BUFFER_SIZE - 1)
220
+ buffer.normal!
221
+ buffer.strip!
222
+ if buffer.empty?
223
+ raise(Au3Error, "Unknown error occured while retrieving process ID. Does the window exist?")
224
+ else
225
+ buffer.to_i
226
+ end
227
+ end
228
+
229
+ #Returns the integer composition of the states
230
+ #- exists (1)
231
+ #- visible (2)
232
+ #- enabled (4)
233
+ #- active (8)
234
+ #- minimized (16)
235
+ #- maximized (32)
236
+ #Use the bit-wise AND operator & to check for a specific state.
237
+ #Or just use one of the predefined methods #exists?, #visible?,
238
+ ##enabled?, #active?, #minimized? and #maximized?.
239
+ def state
240
+ Window.functions[__method__] ||= AU3_Function.new("WinGetState", 'SS', 'L')
241
+ state = Window.functions[__method__].call(@title.wide, @text.wide)
242
+ raise_unfound if AutoItX3.last_error == 1
243
+ state
244
+ end
245
+
246
+ #Returns true if +self+ is shown on the screen.
247
+ def visible?
248
+ (state & 2) == 2
249
+ end
250
+
251
+ #Returns true if +self+ is enabled (i.e. it can receive input).
252
+ def enabled?
253
+ (state & 4) == 4
254
+ end
255
+
256
+ #Returns true if +self+ is minimized to the taskbar.
257
+ def minimized?
258
+ (state & 16) == 16
259
+ end
260
+
261
+ #Returns true if +self+ is maximized to full screen size.
262
+ def maximized?
263
+ (state & 32) == 32
264
+ end
265
+
266
+ #Returns the text read from a window. This method doesn't query the @text instance
267
+ #variable, rather it calls the AU3_WinGetText function.
268
+ def text
269
+ Window.functions[__method__] ||= AU3_Function.new("WinGetText", 'SSPI')
270
+ buffer = " " * AutoItX3::BUFFER_SIZE
271
+ buffer.wide!
272
+ Window.functions[__method__].call(@title.wide, @text.wide, buffer, AutoItX3::BUFFER_SIZE - 1)
273
+ buffer.normal.strip
274
+ end
275
+
276
+ #Returns the title read from a window. This method does not
277
+ #affect or even use the value of @title, that means you can use
278
+ #+title+ to retrieve titles from a window if you're working with the
279
+ #advanced window mode.
280
+ def title
281
+ Window.functions[__method__] ||= AU3_Function.new("WinGetTitle", 'SSPI')
282
+ buffer = " " * AutoItX3::BUFFER_SIZE
283
+ buffer.wide!
284
+ Window.functions[__method__].call(@title.wide, @text.wide, buffer, AutoItX3::BUFFER_SIZE - 1)
285
+ buffer.normal.strip
286
+ end
287
+
288
+ #Kills +self+. This method forces a window to close if it doesn't close
289
+ #quickly enough (in contrary to #close which waits for user actions
290
+ #if neccessary). Some windows cannot be +kill+ed (notably
291
+ #Windows Explorer windows).
292
+ def kill
293
+ Window.functions[__method__] ||= AU3_Function.new("WinKill", 'SS', 'L')
294
+ Window.functions[__method__].call(@title.wide, @text.wide)
295
+ nil
296
+ end
297
+
298
+ #Clicks the specified item in the specified menu. You may specify up to seven
299
+ #submenus.
300
+ def select_menu_item(menu, *items)
301
+ Window.functons[__method__] ||= AU3_Function.new("WinMenuSelectItem", 'SSSSSSSSSS', 'L')
302
+ raise(ArgumentError, "Wrong number of arguments, maximum is seven items!") if items.size > 7 #(menu is the 8th)
303
+ result = Window.functions[__method__].call(@title.wide, @text.wide, menu.wide, *items.map{|item| item.wide})
304
+ raise_unfound if result == 0
305
+ nil
306
+ end
307
+
308
+ #Moves a window (and optionally resizes it). This does not work
309
+ #with minimized windows.
310
+ def move(x, y, width = -1, height = -1)
311
+ Window.functions[__method__] ||= AU3_Function.new("WinMove", 'SSLLLL', 'L')
312
+ Window.functions[__method__].call(@title.wide, @text.wide, x, y, width, height)
313
+ nil
314
+ end
315
+
316
+ #Turn the TOPMOST flag of +self+ on or off. If activated, the window
317
+ #will stay on top above all other windows.
318
+ def set_on_top=(val)
319
+ Window.functions[__method__] ||= AU3_Function.new("WinSetOnTop", 'SSL', 'L')
320
+ Window.functions[__method__].call(@title.wide, @text.wide, !!val)
321
+ val
322
+ end
323
+
324
+ #Sets +self+'s window state to one of the SW_* constants.
325
+ def state=(val)
326
+ Window.functions[__method__] ||= AU3_Function.new("WinSetState", 'SSL', 'L')
327
+ Window.functions[__method__].call(@title.wide, @text.wide, val)
328
+ val
329
+ end
330
+
331
+ #Renames +self+. This does not change the internal @title
332
+ #instance variable, so you can use this with the
333
+ #advanced window mode.
334
+ def title=(val)
335
+ Window.functions[__method__] ||= AU3_Function.new("WinSetTitle", 'SSS', 'L')
336
+ Window.functions[__method__].call(@title.wide, @text.wide, val.wide)
337
+ val
338
+ end
339
+
340
+ #call-seq:
341
+ # AutoItX3::Window#trans = val ==> val
342
+ # AutoItX3::Window#transparency = val ==> val
343
+ #
344
+ #Sets the transparency of +self+ or raises a NotImplementedError
345
+ #if the OS is Windows Millenium or older.
346
+ def trans=(val)
347
+ Window.functions[__method__] ||= AU3_Function.new("WinSetTrans", 'SSL', 'L')
348
+ if Window.functions[__method__].call(@title.wide, @text.wide, val) == 0
349
+ raise(NotImplementedError, "The method trans= is only implemented in Win2000 and newer!")
350
+ end
351
+ val
352
+ end
353
+ alias transparency= trans=
354
+
355
+ #Waits for +self+ to exist. This method calls Window's class
356
+ #method wait, so see Window.wait for more information.
357
+ def wait(timeout = 0)
358
+ Window.wait(@title, @text, timeout)
359
+ end
360
+
361
+ #Waits for +self+ to be the active (that is, get the input focus).
362
+ def wait_active(timeout = 0)
363
+ Window.functions[__method__] ||= AU3_Function.new("WinWaitActive", 'SSL', 'L')
364
+ Window.functions[__method__].call(@title.wide, @text.wide, timeout) != 0
365
+ end
366
+
367
+ #Waits for +self+ to be closed.
368
+ def wait_close(timeout = 0)
369
+ Window.functions[__method__] ||= AU3_Function.new("WinWaitClose", 'SSL', 'L')
370
+ Window.functions[__method__].call(@title.wide, @text.wide, timeout) != 0
371
+ end
372
+
373
+ #Waits for +self+ to lose the input focus.
374
+ def wait_not_active(timeout = 0)
375
+ Window.functions[__method__] ||= AU3_Function.new("WinWaitNotActive", 'SSL', 'L')
376
+ Window.functions[__method__].call(@title.wide, @text.wide, timeout) != 0
377
+ end
378
+
379
+ #Returns the actually focused control in +self+, a AutoItX3::Control object.
380
+ #Note that if the owning window doesn't have the input focus, you'll get an
381
+ #unusable Control object back.
382
+ def focused_control
383
+ Window.functions[__method__] ||= AU3_Function.new("ControlGetFocus", 'SSPI')
384
+ buffer = " " * AutoItX3::BUFFER_SIZE
385
+ buffer.wide!
386
+ Window.functions[__method__].call(@title.wide, @text.wide, buffer, AutoItX3::BUFFER_SIZE - 1)
387
+ AutoItX3::Control.new(@title, @text, buffer.normal.strip)
388
+ end
389
+
390
+ #Reads the text of the statusbar at position +part+. This method
391
+ #raises an Au3Error if there's no statusbar, it's not a mscommon
392
+ #statusbar or if you try to read a position out of range.
393
+ def statusbar_text(part = 1)
394
+ Window.functions[__method__] ||= AU3_Function.new("StatusbarGetText", 'SSLPI')
395
+ buffer = " " * AutoItX3::BUFFER_SIZE
396
+ buffer.wide!
397
+ Window.functions[__method__].call(@title.wide, @text.wide, part, buffer, AutoItX3::BUFFER_SIZE - 1)
398
+ raise(Au3Error, "Couldn't read statusbar text!") if AutoItX3.last_error == 1
399
+ buffer.normal.strip
400
+ end
401
+
402
+ private
403
+
404
+ #Raises an error that says, that +self+ couldn't be found.
405
+ def raise_unfound
406
+ raise(Au3Error, "Unable to find a window with title '#{@title}' and text '#{@text}'!", caller)
407
+ end
408
+
409
+ end
410
+
411
+ end