winwindow 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1364 @@
1
+ require(File.join(File.dirname(__FILE__), 'winwindow','ext'))
2
+
3
+ # WinWindow: A Ruby library to wrap windows API calls relating to hWnd window handles.
4
+ #
5
+ # The WinWindow class represents a window, wrapping a hWnd and exposing a Ruby API corresponding to
6
+ # many useful Windows API functions relating to a hWnd.
7
+ class WinWindow
8
+ # :stopdoc:
9
+
10
+ require 'enumerator'
11
+ Enumerator = Object.const_defined?('Enumerator') ? ::Enumerator : Enumerable::Enumerator # :nodoc:
12
+
13
+ # todo:
14
+ # * GetTitleBarInfo http://msdn.microsoft.com/en-us/library/ms633513(VS.85).aspx
15
+ # * GetWindowInfo http://msdn.microsoft.com/en-us/library/ms633516(VS.85).aspx http://msdn.microsoft.com/en-us/library/ms632610(VS.85).aspx
16
+ # * ? ShowOwnedPopups http://msdn.microsoft.com/en-us/library/ms633547(VS.85).aspx
17
+ # * FindWindow http://msdn.microsoft.com/en-us/library/ms633499(VS.85).aspx
18
+ # * other useful stuff, see http://msdn.microsoft.com/en-us/library/ms632595(VS.85).aspx
19
+ # * expand SendMessage / PostMessage http://msdn.microsoft.com/en-us/library/ms644950(VS.85).aspx http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx
20
+
21
+ # :startdoc:
22
+
23
+ # base class from which WinWindow Errors inherit.
24
+ class Error < StandardError;end
25
+ # exception which is raised when an underlying method of the operating system encounters an error
26
+ class SystemError < Error
27
+ attr_reader :function
28
+ attr_reader :code
29
+ attr_reader :system_message
30
+ def initialize(message, stuff={})
31
+ super(message)
32
+ @function=stuff[:function]
33
+ @code=stuff[:code]
34
+ @system_message=stuff[:system_message]
35
+ end
36
+ end
37
+ # exception raised when a window which is expected to exist does not exist
38
+ class NotExistsError < Error;end
39
+ # exception raised when a thing was expected to match another thing but didn't
40
+ class MatchError < Error;end
41
+
42
+ # :stopdoc:
43
+
44
+ # this module exists because I've implemented this library for DL, for FFI, and for Win32::API.
45
+ # Getting tired of changing everything everywhere, now it just takes changes to Types,
46
+ # and a few methods (use_lib, attach, callback) to switch to another library.
47
+ module AttachLib # :nodoc: all
48
+ IsWin64=nil # TODO/FIX: detect this!
49
+
50
+ #=begin
51
+ # here begins the FFI version. this one needs to hack improperly into FFI's internals, bypassing its
52
+ # broken API for #callback which doesn't set the calling convention, causing segfaults.
53
+ begin
54
+ require 'rubygems'
55
+ # prefer 0.5.4 because of bug: http://github.com/ffi/ffi/issues/issue/59
56
+ gem 'ffi', '= 0.5.4'
57
+ CanTrustFormatMessage = true
58
+ rescue LoadError
59
+ # if we didn't get 0.5.4, we should assume that calling FormatMessage will segfault.
60
+ CanTrustFormatMessage = false
61
+ end
62
+ require 'ffi'
63
+
64
+ # types that FFI recognizes
65
+ Types=[:char, :uchar, :int, :uint, :short, :ushort, :long, :ulong, :void, :pointer, :string].inject({}) do |type_hash, type|
66
+ type_hash[type]=type
67
+ type_hash
68
+ end
69
+
70
+ def self.extended(extender)
71
+ ffi_module=Module.new
72
+ ffi_module.send(:extend, FFI::Library)
73
+ extender.send(:instance_variable_set, '@ffi_module', ffi_module)
74
+ end
75
+ def use_lib(lib)
76
+ @ffi_module.ffi_lib lib
77
+ @ffi_module.ffi_convention :stdcall
78
+ end
79
+ # this takes arguments in the order that they're given in c/c++ so that signatures look kind of like the source
80
+ def attach(return_type, function_name, *arg_types)
81
+ @ffi_module.attach_function(function_name, arg_types.map{|arg_type| Types[arg_type] }, Types[return_type])
82
+ metaclass=class << self;self;end
83
+ ffi_module=@ffi_module
84
+ metaclass.send(:define_method, function_name) do |*args|
85
+ ffi_module.send(function_name, *args)
86
+ end
87
+ nil
88
+ end
89
+ # this takes arguments like #attach, but with a name for the callback's type on the front.
90
+ def callback(callback_type_name, return_type, callback_method_name, *arg_types)
91
+
92
+ Types[callback_type_name]=callback_type_name
93
+
94
+ #@ffi_module.callback(callback_type_name, arg_types.map{|type| Types[type]}, Types[return_type])
95
+
96
+ # we do not call @ffi_module.callback here, because it is broken. we need to pass the convention ourselves in the options hash.
97
+ # this is adapted from: http://gist.github.com/256660
98
+ options={}
99
+ types=Types
100
+ @ffi_module.instance_eval do
101
+ options[:convention] = defined?(@ffi_convention) ? @ffi_convention : :default
102
+ options[:enums] = @ffi_enums if defined?(@ffi_enums)
103
+
104
+ cb = FFI::CallbackInfo.new(find_type(types[return_type]), arg_types.map{|e| find_type(types[e]) }, options)
105
+
106
+ @ffi_callbacks = Hash.new unless defined?(@ffi_callbacks)
107
+ @ffi_callbacks[callback_type_name] = cb
108
+ end
109
+ #options[:convention] = @ffi_module.instance_variable_defined?('@ffi_convention') ? @ffi_module.instance_variable_get('@ffi_convention') : :default
110
+ #options[:enums] = @ffi_module.instance_variable_get('@ffi_enums') if @ffi_module.instance_variable_defined?('@ffi_enums')
111
+ #unless @ffi_module.instance_variable_defined?('@ffi_callbacks')
112
+ # @ffi_module.instance_variable_set('@ffi_callbacks', cb)
113
+ #end
114
+
115
+ # perform some hideous class_eval'ing to dynamically define the callback method such that it will take a block
116
+ metaclass=class << self;self;end
117
+
118
+ # FFI just takes the block itself. don't need anything fancy here.
119
+ metaclass.class_eval("def #{callback_method_name}(&block)
120
+ block
121
+ end
122
+ def remove_#{callback_method_name}(callback_method)
123
+ # FFI has no support for removing callbacks?
124
+ nil
125
+ end")
126
+ # don't use define_method as this will be called from an ensure block which segfaults ruby 1.9.1. see http://redmine.ruby-lang.org/issues/show/2728
127
+ #metaclass.send(:define_method, "remove_"+callback_method_name.to_s) do |callback_method|
128
+ # nil
129
+ #end
130
+ nil
131
+ end
132
+ #=end
133
+ =begin
134
+ # here begins the Win32::API version. this one doesn't work because of a hard-coded limit on
135
+ # callbacks in win32/api.c combined with a lack of any capacity to remove any callbacks.
136
+ require 'win32/api'
137
+
138
+ # basic types that Win32::API recognizes
139
+ Types={ :char => 'I', # no 8-bit type in Win32::API?
140
+ :uchar => 'I', # no unsigned types in Win32::API?
141
+ :int => 'I',
142
+ :uint => 'I',
143
+ :long => 'L',
144
+ :ulong => 'L',
145
+ :void => 'V',
146
+ :pointer => 'P',
147
+ :callback => 'K',
148
+ :string => 'P', # 'S' works here on mingw32, but not on mswin32
149
+ }
150
+
151
+ def use_lib(lib)
152
+ @lib=lib
153
+ end
154
+ # this takes arguments in the order that they're given in c/c++ so that signatures look kind of like the source
155
+ def attach(return_type, function_name, *arg_types)
156
+ the_function=Win32::API.new(function_name.to_s, arg_types.map{|arg_type| Types[arg_type] }.join(''), Types[return_type], @lib)
157
+ metaclass=class << self;self;end
158
+ metaclass.send(:define_method, function_name) do |*args|
159
+ the_function.call(*args)
160
+ end
161
+ nil
162
+ end
163
+ # this takes arguments like #attach, but with a name for the callback's type on the front.
164
+ def callback(callback_type_name, return_type, callback_method_name, *arg_types)
165
+ Types[callback_type_name]=Types[:callback]
166
+
167
+ # perform some hideous class_eval'ing to dynamically define the callback method such that it will take a block
168
+ metaclass=class << self;self;end
169
+ metaclass.class_eval("def #{callback_method_name}(&block)
170
+ #{callback_method_name}_with_arg_stuff_in_scope(block)
171
+ end")
172
+ types=Types
173
+ metaclass.send(:define_method, callback_method_name.to_s+"_with_arg_stuff_in_scope") do |block|
174
+ return Win32::API::Callback.new(arg_types.map{|t| types[t]}.join(''), types[return_type], &block)
175
+ end
176
+ def remove_#{callback_method_name}(callback_method)
177
+ # Win32::API has no support for removing callbacks?
178
+ nil
179
+ end")
180
+ # don't use define_method as this will be called from an ensure block which segfaults ruby 1.9.1. see http://redmine.ruby-lang.org/issues/show/2728
181
+ #metaclass.send(:define_method, "remove_"+callback_method_name.to_s) do |callback_method|
182
+ # nil
183
+ #end
184
+ nil
185
+ end
186
+ =end
187
+ def self.add_type(hash)
188
+ hash.each_pair do |key, value|
189
+ unless Types.key?(value)
190
+ raise "unrecognized type #{value.inspect}"
191
+ end
192
+ Types[key]=Types[value]
193
+ end
194
+ end
195
+
196
+ end
197
+ Types=AttachLib::Types
198
+ # types from http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx
199
+ AttachLib.add_type :buffer_in => :string
200
+ AttachLib.add_type :buffer_out => :pointer
201
+ AttachLib.add_type :HWND => :ulong # this is a lie. really void*, but easier to deal with as a long.
202
+ AttachLib.add_type :HDC => :pointer
203
+ AttachLib.add_type :LPSTR => :pointer # char*
204
+ AttachLib.add_type :LPWSTR => :pointer # wchar_t*
205
+ AttachLib.add_type :LPCSTR => :buffer_in # const char*
206
+ AttachLib.add_type :LPCWSTR => :pointer # const wchar_t*
207
+ AttachLib.add_type :LONG_PTR => (AttachLib::IsWin64 ? :int64 : :long) # TODO/FIX: there is no :int64 type defined on Win32::API
208
+ AttachLib.add_type :LRESULT => :LONG_PTR
209
+ AttachLib.add_type :WPARAM => (AttachLib::IsWin64 ? :uint64 : :uint) # TODO/FIX: no :uint64 on Win3::API
210
+ AttachLib.add_type :LPARAM => :pointer #:LONG_PTR # this is supposed to be a LONG_PTR (a long type for pointer precision), but casting around is annoying - just going to use it as a pointer.
211
+ AttachLib.add_type :BOOL => :int
212
+ AttachLib.add_type :BYTE => :uchar
213
+ AttachLib.add_type :WORD => :ushort
214
+ AttachLib.add_type :DWORD => :ulong
215
+ AttachLib.add_type :LPRECT => :pointer
216
+ AttachLib.add_type :LPDWORD => :pointer
217
+ module WinUser # :nodoc: all
218
+ extend AttachLib
219
+ use_lib 'user32'
220
+
221
+ attach :int, :GetWindowTextA, :HWND, :LPSTR, :int
222
+ attach :int, :GetWindowTextW, :HWND, :LPWSTR, :int
223
+ attach :int, :GetWindowTextLengthA, :HWND
224
+ attach :int, :GetWindowTextLengthW, :HWND
225
+ attach :LRESULT, :SendMessageA, :HWND, :uint, :WPARAM, :LPARAM
226
+ attach :LRESULT, :SendMessageW, :HWND, :uint, :WPARAM, :LPARAM
227
+ attach :BOOL, :PostMessageA, :HWND, :uint, :WPARAM, :LPARAM
228
+ attach :BOOL, :PostMessageW, :HWND, :uint, :WPARAM, :LPARAM
229
+ attach :BOOL, :SetWindowTextA, :HWND, :LPCSTR
230
+ attach :BOOL, :SetWindowTextW, :HWND, :LPCWSTR
231
+ attach :HWND, :GetWindow, :HWND, :uint
232
+ attach :HWND, :GetAncestor, :HWND, :uint
233
+ attach :HWND, :GetLastActivePopup, :HWND
234
+ attach :HWND, :GetTopWindow, :HWND
235
+ attach :HWND, :GetParent, :HWND
236
+ attach :HWND, :SetParent, :HWND, :HWND
237
+ attach :BOOL, :IsChild, :HWND, :HWND
238
+ attach :BOOL, :IsHungAppWindow, :HWND
239
+ attach :BOOL, :IsWindow, :HWND
240
+ attach :BOOL, :IsWindowVisible, :HWND
241
+ attach :BOOL, :IsIconic, :HWND
242
+ attach :BOOL, :SetForegroundWindow, :HWND
243
+ attach :BOOL, :BringWindowToTop, :HWND
244
+ attach :BOOL, :CloseWindow, :HWND
245
+ attach :BOOL, :DestroyWindow, :HWND
246
+ attach :int, :GetClassNameA, :HWND, :LPSTR, :int
247
+ attach :int, :GetClassNameW, :HWND, :LPWSTR, :int
248
+ attach :uint, :RealGetWindowClassA, :HWND, :LPSTR, :uint
249
+ attach :uint, :RealGetWindowClassW, :HWND, :LPWSTR, :uint
250
+ attach :DWORD, :GetWindowThreadProcessId, :HWND, :LPDWORD
251
+ attach :void, :SwitchToThisWindow, :HWND, :BOOL
252
+ attach :BOOL, :LockSetForegroundWindow, :uint
253
+ attach :uint, :MapVirtualKeyA, :uint, :uint
254
+ attach :uint, :MapVirtualKeyW, :uint, :uint
255
+ attach :void, :keybd_event, :BYTE, :BYTE, :DWORD, :pointer
256
+ attach :BOOL, :ShowWindow, :HWND, :int
257
+ attach :BOOL, :EndTask, :HWND, :BOOL, :BOOL
258
+ attach :HWND, :GetForegroundWindow
259
+ attach :HWND, :GetDesktopWindow
260
+ attach :HDC, :GetDC, :HWND
261
+ attach :HDC, :GetWindowDC, :HWND
262
+ attach :int, :ReleaseDC, :HWND, :HDC
263
+
264
+ class Rect < FFI::Struct
265
+ layout :left, :long,
266
+ :top, :long,
267
+ :right, :long,
268
+ :bottom, :long
269
+ end
270
+ attach :BOOL, :GetWindowRect, :HWND, :LPRECT
271
+ attach :BOOL, :GetClientRect, :HWND, :LPRECT
272
+
273
+ callback :WNDENUMPROC, :BOOL, :window_enum_callback, :HWND, :LPARAM
274
+ attach :BOOL, :EnumWindows, :WNDENUMPROC, :LPARAM
275
+ attach :BOOL, :EnumChildWindows, :HWND, :WNDENUMPROC, :LPARAM
276
+ end
277
+ AttachLib.add_type :SIZE_T => (AttachLib::IsWin64 ? :uint64 : :ulong)
278
+
279
+ module WinKernel # :nodoc: all
280
+ extend AttachLib
281
+ use_lib 'kernel32'
282
+ attach :DWORD, :GetLastError
283
+ attach :DWORD, :FormatMessageA, :DWORD, :pointer, :DWORD, :DWORD, :LPSTR, :DWORD
284
+ attach :DWORD, :FormatMessageW, :DWORD, :pointer, :DWORD, :DWORD, :LPWSTR, :DWORD
285
+
286
+ attach :pointer, :GlobalAlloc, :uint, :SIZE_T
287
+ attach :pointer, :GlobalFree, :pointer
288
+ attach :pointer, :GlobalLock, :pointer
289
+ attach :pointer, :GlobalUnlock, :pointer
290
+ end
291
+ AttachLib.add_type :HGDIOBJ => :pointer
292
+ AttachLib.add_type :HBITMAP => :pointer
293
+ AttachLib.add_type :LPBITMAPINFO => :pointer
294
+ module WinGDI # :nodoc: all
295
+ extend AttachLib
296
+ use_lib 'gdi32'
297
+ attach :HDC, :CreateCompatibleDC, :HDC
298
+ attach :BOOL, :DeleteDC, :HDC
299
+ attach :int, :GetDeviceCaps, :HDC, :int
300
+ attach :HBITMAP, :CreateCompatibleBitmap, :HDC, :int, :int
301
+ attach :HGDIOBJ, :SelectObject, :HDC, :HGDIOBJ
302
+ attach :BOOL, :DeleteObject, :HGDIOBJ
303
+ attach :BOOL, :BitBlt, :HDC, :int, :int, :int, :int, :HDC, :int, :int, :DWORD
304
+ attach :int, :GetDIBits, :HDC, :HBITMAP, :uint, :uint, :pointer, :LPBITMAPINFO, :uint
305
+
306
+ class BITMAPINFOHEADER < FFI::Struct
307
+ layout(
308
+ :Size, :int32,
309
+ :Width, :int32,
310
+ :Height, :int32,
311
+ :Planes, :int16,
312
+ :BitCount, :int16,
313
+ :Compression, :int32,
314
+ :SizeImage, :int32,
315
+ :XPelsPerMeter, :int32,
316
+ :YPelsPerMeter, :int32,
317
+ :ClrUsed, :int32,
318
+ :ClrImportant, :int32
319
+ )
320
+ end
321
+ class BITMAPFILEHEADER < FFI::Struct
322
+ layout(
323
+ :Type, :int16, 0,
324
+ :Size, :int32, 2,
325
+ :Reserved1, :int16, 6,
326
+ :Reserved2, :int16, 8,
327
+ :OffBits, :int32, 10
328
+ )
329
+ end
330
+ # for some reason size is returned as 16; should be 14
331
+ class << FFI::Struct
332
+ def real_size
333
+ layout.fields.inject(0) do |sum, field|
334
+ sum+field.size
335
+ end
336
+ end
337
+ end
338
+ end
339
+
340
+ WM_CLOSE = 0x0010
341
+ WM_KEYDOWN = 0x0100
342
+ WM_KEYUP = 0x0101
343
+ WM_CHAR = 0x0102
344
+ BM_CLICK = 0x00F5
345
+ WM_COMMAND = 0x0111
346
+ WM_SETTEXT = 0x000C
347
+ WM_GETTEXT = 0x000D
348
+ WM_GETTEXTLENGTH = 0xE
349
+
350
+ #--
351
+ # GetWindows constants
352
+ GW_HWNDFIRST = 0
353
+ GW_HWNDLAST = 1
354
+ GW_HWNDNEXT = 2
355
+ GW_HWNDPREV = 3
356
+ GW_OWNER = 4
357
+ GW_CHILD = 5
358
+ GW_ENABLEDPOPUP = 6
359
+ GW_MAX = 6
360
+
361
+ #--
362
+ # GetAncestor constants
363
+ GA_PARENT = 1
364
+ GA_ROOT = 2
365
+ GA_ROOTOWNER = 3
366
+
367
+ #--
368
+ # ShowWindow constants - http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
369
+ SW_HIDE = 0 # Hides the window and activates another window.
370
+ SW_SHOWNORMAL = 1 # Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time.
371
+ SW_SHOWMINIMIZED = 2 # Activates the window and displays it as a minimized window.
372
+ SW_SHOWMAXIMIZED = 3 # Activates the window and displays it as a maximized window.
373
+ SW_MAXIMIZE = 3 # Maximizes the specified window.
374
+ #--
375
+ # there seems to be no distinct SW_MAXIMIZE (but there is a distinct SW_MINIMIZE), just the same as SW_SHOWMAXIMIZED
376
+ # some references define SW_MAXIMIZE as 11, which seems to just be wrong; that is correctly SW_FORCEMINIMIZE
377
+ SW_SHOWNOACTIVATE = 4 # Displays a window in its most recent size and position. This value is similar to SW_SHOWNORMAL, except the window is not actived.
378
+ SW_SHOW = 5 # Activates the window and displays it in its current size and position.
379
+ SW_MINIMIZE = 6 # Minimizes the specified window and activates the next top-level window in the Z order.
380
+ SW_SHOWMINNOACTIVE = 7 # Displays the window as a minimized window. This value is similar to SW_SHOWMINIMIZED, except the window is not activated.
381
+ SW_SHOWNA = 8 # Displays the window in its current size and position. This value is similar to SW_SHOW, except the window is not activated.
382
+ SW_RESTORE = 9 # Activates and displays the window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when restoring a minimized window.
383
+ SW_SHOWDEFAULT = 10 # Sets the show state based on the SW_ value specified in the STARTUPINFO structure passed to the CreateProcess function by the program that started the application.
384
+ SW_FORCEMINIMIZE = 11 # Windows 2000/XP: Minimizes a window, even if the thread that owns the window is not responding. This flag should only be used when minimizing windows from a different thread.
385
+
386
+ WIN_TRUE=-1
387
+ WIN_FALSE=0
388
+
389
+ # :startdoc:
390
+
391
+ # handle to the window - a positive integer. (properly, this is a pointer, but we deal with it as a number.)
392
+ attr_reader :hwnd
393
+
394
+ # creates a WinWindow from a given hWnd handle (integer)
395
+ #
396
+ # raises ArgumentError if the hWnd is not an Integer greater than 0
397
+ def initialize(hwnd)
398
+ raise ArgumentError, "hwnd must be an integer greater than 0; got #{hwnd.inspect} (#{hwnd.class})" unless hwnd.is_a?(Integer) && hwnd > 0
399
+ @hwnd=hwnd
400
+ end
401
+
402
+ def inspect
403
+ retrieve_text
404
+ class_name
405
+ Object.instance_method(:inspect).bind(self).call
406
+ end
407
+
408
+ def pretty_print(pp)
409
+ retrieve_text
410
+ class_name
411
+ pp.pp_object(self)
412
+ end
413
+
414
+ # retrieves the text of this window's title bar (if it has one). If this is a control, the text
415
+ # of the control is retrieved. However, #text cannot retrieve the text of a control in another
416
+ # application (see #retrieve_text)
417
+ #
418
+ # http://msdn.microsoft.com/en-us/library/ms633520(VS.85).aspx
419
+ def text
420
+ buff_size=text_length+1
421
+ buff="\001"*buff_size
422
+ len= WinUser.GetWindowTextA(hwnd, buff, buff_size)
423
+ @text=buff[0...len]
424
+ end
425
+
426
+ # length of the window text, see #text
427
+ #
428
+ # similar to #text, cannot retrieve the text of a control in another application - see
429
+ # #retrieve_text, #retrieve_text_length
430
+ #
431
+ # http://msdn.microsoft.com/en-us/library/ms633521(VS.85).aspx
432
+ def text_length
433
+ len= WinUser.GetWindowTextLengthA(hwnd)
434
+ len
435
+ end
436
+
437
+ # This is similar to #text
438
+ # that one is GetWindowText(hwnd)
439
+ # this one is SendMessage(hwnd, WM_GETTEXT)
440
+ # differences are documented here: http://msdn.microsoft.com/en-us/magazine/cc301438.aspx
441
+ # and here: http://blogs.msdn.com/oldnewthing/archive/2003/08/21/54675.aspx
442
+ def retrieve_text
443
+ buff_size=retrieve_text_length+1
444
+ buff=" "*buff_size
445
+ len= WinUser.SendMessageA(hwnd, WM_GETTEXT, buff_size, buff)
446
+ @text=buff[0...len]
447
+ end
448
+
449
+ # similar to #text_length; differences between that and this are the same as between #text and
450
+ # #retrieve_text
451
+ def retrieve_text_length
452
+ len= WinUser.SendMessageA(hwnd, WM_GETTEXTLENGTH, 0, nil)
453
+ len
454
+ end
455
+
456
+ # changes the text of the specified window's title bar (if it has one). If the specified window
457
+ # is a control, the text of the control is changed.
458
+ # However, #set_text! cannot change the text of a control in another application (see #send_set_text!)
459
+ #
460
+ # http://msdn.microsoft.com/en-us/library/ms633546(VS.85).aspx
461
+ def set_text!(text)
462
+ set=WinUser.SetWindowTextA(hwnd, text)
463
+ set != WIN_FALSE
464
+ end
465
+
466
+ # sets text by sending WM_SETTEXT message. this different than #set_text! in the same way that
467
+ # #retrieve_text is different than #text
468
+ def send_set_text!(text)
469
+ ret=WinUser.SendMessageA(hwnd, WM_SETTEXT, 0, text.dup)
470
+ nil
471
+ end
472
+
473
+ # The retrieved handle identifies the enabled popup window owned by the specified window
474
+ # (the search uses the first such window found using GW_HWNDNEXT); otherwise, if there
475
+ # are no enabled popup windows, nil is returned.
476
+ #
477
+ # http://msdn.microsoft.com/en-us/library/ms633515(VS.85).aspx
478
+ def enabled_popup
479
+ popup_hwnd=WinUser.GetWindow(hwnd, GW_ENABLEDPOPUP)
480
+ @enabled_popup= popup_hwnd > 0 && popup_hwnd != self.hwnd ? self.class.new(popup_hwnd) : nil
481
+ end
482
+
483
+ # The retrieved handle identifies the specified window's owner window, if any.
484
+ #
485
+ # http://msdn.microsoft.com/en-us/library/ms633515(VS.85).aspx
486
+ def owner
487
+ owner_hwnd=WinUser.GetWindow(hwnd, GW_OWNER)
488
+ @owner= owner_hwnd > 0 ? self.class.new(owner_hwnd) : nil
489
+ end
490
+
491
+ # Retrieves the parent window. This does not include the owner, as it does with #parent
492
+ #
493
+ # http://msdn.microsoft.com/en-us/library/ms633502(VS.85).aspx
494
+ def ancestor_parent
495
+ ret_hwnd=WinUser.GetAncestor(hwnd, GA_PARENT)
496
+ @ancestor_parent= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
497
+ end
498
+
499
+ # Retrieves the root window by walking the chain of parent windows.
500
+ #
501
+ # http://msdn.microsoft.com/en-us/library/ms633502(VS.85).aspx
502
+ def ancestor_root
503
+ ret_hwnd=WinUser.GetAncestor(hwnd, GA_ROOT)
504
+ @ancestor_root= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
505
+ end
506
+
507
+ # Retrieves the owned root window by walking the chain of parent and owner windows returned by
508
+ # GetParent.
509
+ #
510
+ # http://msdn.microsoft.com/en-us/library/ms633502(VS.85).aspx
511
+ def ancestor_root_owner
512
+ ret_hwnd=WinUser.GetAncestor(hwnd, GA_ROOTOWNER)
513
+ @ancestor_root_owner= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
514
+ end
515
+
516
+ # determines which pop-up window owned by this window was most recently active
517
+ #
518
+ # http://msdn.microsoft.com/en-us/library/ms633507(VS.85).aspx
519
+ def last_active_popup
520
+ ret_hwnd=WinUser.GetLastActivePopup(hwnd)
521
+ @last_active_popup= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
522
+ end
523
+
524
+ # examines the Z order of the child windows associated with self and retrieves a handle to the
525
+ # child window at the top of the Z order
526
+ #
527
+ # http://msdn.microsoft.com/en-us/library/ms633514(VS.85).aspx
528
+ def top_window
529
+ ret_hwnd= WinUser.GetTopWindow(hwnd)
530
+ @top_window= ret_hwnd > 0 ? self.class.new(ret_hwnd) : nil
531
+ end
532
+
533
+ # retrieves a handle to this window's parent or owner
534
+ #
535
+ # http://msdn.microsoft.com/en-us/library/ms633510(VS.85).aspx
536
+ def parent
537
+ parent_hwnd=WinUser.GetParent(hwnd)
538
+ @parent= parent_hwnd > 0 ? self.class.new(parent_hwnd) : nil
539
+ end
540
+
541
+ # changes the parent window of this child window
542
+ #
543
+ # http://msdn.microsoft.com/en-us/library/ms633541(VS.85).aspx
544
+ def set_parent!(parent)
545
+ parent_hwnd= parent.is_a?(self.class) ? parent.hwnd : parent
546
+ new_parent=WinUser.SetParent(hwnd, parent_hwnd)
547
+ new_parent > 0 ? self.class.new(new_parent) : nil
548
+ end
549
+
550
+ # tests whether a window is a child window or descendant window of a specified parent window.
551
+ # A child window is the direct descendant of a specified parent window if that parent window
552
+ # is in the chain of parent windows; the chain of parent windows leads from the original
553
+ # overlapped or pop-up window to the child window.
554
+ #
555
+ # http://msdn.microsoft.com/en-us/library/ms633524(VS.85).aspx
556
+ def child_of?(parent)
557
+ parent_hwnd= parent.is_a?(self.class) ? parent.hwnd : parent
558
+ child=WinUser.IsChild(parent_hwnd, hwnd)
559
+ child!=WIN_FALSE
560
+ end
561
+
562
+ # determine if Microsoft Windows considers that a specified application is not responding. An
563
+ # application is considered to be not responding if it is not waiting for input, is not in
564
+ # startup processing, and has not called PeekMessage within the internal timeout period of 5
565
+ # seconds.
566
+ #
567
+ # http://msdn.microsoft.com/en-us/library/ms633526.aspx
568
+ def hung_app?
569
+ hung=WinUser.IsHungAppWindow(hwnd)
570
+ hung != WIN_FALSE
571
+ end
572
+
573
+ # retrieves the name of the class to which this window belongs
574
+ #
575
+ # http://msdn.microsoft.com/en-us/library/ms633582(VS.85).aspx
576
+ def class_name
577
+ buff_size=256
578
+ buff=" "*buff_size
579
+ len=WinUser.GetClassNameA(hwnd, buff, buff_size)
580
+ @class_name=buff.to_s[0...len]
581
+ end
582
+
583
+ # retrieves a string that specifies the window type
584
+ #
585
+ # http://msdn.microsoft.com/en-us/library/ms633538(VS.85).aspx
586
+ def real_class_name
587
+ buff_size=256
588
+ buff=" "*buff_size
589
+ len=WinUser.RealGetWindowClassA(hwnd, buff, buff_size)
590
+ @real_class_name=buff.to_s[0...len]
591
+ end
592
+
593
+ # returns the identifier of the thread that created the window
594
+ #
595
+ # http://msdn.microsoft.com/en-us/library/ms633522%28VS.85%29.aspx
596
+ def thread_id
597
+ WinUser.GetWindowThreadProcessId(hwnd, nil)
598
+ end
599
+
600
+ # returns the process identifier that created this window
601
+ #
602
+ # http://msdn.microsoft.com/en-us/library/ms633522%28VS.85%29.aspx
603
+ def process_id
604
+ lpdwProcessId=FFI::MemoryPointer.new(Types[:LPDWORD])
605
+ WinUser.GetWindowThreadProcessId(hwnd, lpdwProcessId)
606
+ lpdwProcessId.get_ulong(0)
607
+ end
608
+
609
+ # determines whether the specified window handle identifies an existing window
610
+ #
611
+ # http://msdn.microsoft.com/en-us/library/ms633528(VS.85).aspx
612
+ def exists?
613
+ ret=WinUser.IsWindow(hwnd)
614
+ ret != WIN_FALSE
615
+ end
616
+
617
+ # visibility state of the specified window
618
+ #
619
+ # http://msdn.microsoft.com/en-us/library/ms633530(VS.85).aspx
620
+ def visible?
621
+ ret=WinUser.IsWindowVisible(hwnd)
622
+ ret != WIN_FALSE
623
+ end
624
+
625
+ # whether the window is minimized (iconic).
626
+ #
627
+ # http://msdn.microsoft.com/en-us/library/ms633527(VS.85).aspx
628
+ def iconic?
629
+ ret=WinUser.IsIconic(hwnd)
630
+ ret != WIN_FALSE
631
+ end
632
+ alias minimized? iconic?
633
+
634
+ # switch focus and bring to the foreground
635
+ #
636
+ # the argument alt_tab, if true, indicates that the window is being switched to using the
637
+ # Alt/Ctl+Tab key sequence. This argument should be false otherwise.
638
+ #
639
+ # http://msdn.microsoft.com/en-us/library/ms633553(VS.85).aspx
640
+ def switch_to!(alt_tab=false)
641
+ WinUser.SwitchToThisWindow(hwnd, alt_tab ? WIN_TRUE : WIN_FALSE)
642
+ end
643
+
644
+ # puts the thread that created the specified window into the foreground and activates the
645
+ # window. Keyboard input is directed to the window, and various visual cues are changed for the
646
+ # user. The system assigns a slightly higher priority to the thread that created the foreground
647
+ # window than it does to other threads.
648
+ #
649
+ # If the window was brought to the foreground, the return value is true.
650
+ #
651
+ # If the window was not brought to the foreground, the return value is false.
652
+ #
653
+ # http://msdn.microsoft.com/en-us/library/ms633539(VS.85).aspx
654
+ def set_foreground!
655
+ ret= WinUser.SetForegroundWindow(hwnd)
656
+ ret != WIN_FALSE
657
+ end
658
+
659
+ # returns true if this is the same Window that is returned from WinWindow.foreground_window
660
+ def foreground?
661
+ self==self.class.foreground_window
662
+ end
663
+
664
+ # :stopdoc:
665
+ LSFW_LOCK = 1
666
+ LSFW_UNLOCK = 2
667
+ # :startdoc:
668
+
669
+ # The foreground process can call the #lock_set_foreground_window function to disable calls to
670
+ # the #set_foreground! function.
671
+ #
672
+ # Disables calls to #set_foreground!
673
+ #
674
+ # http://msdn.microsoft.com/en-us/library/ms633532%28VS.85%29.aspx
675
+ def self.lock_set_foreground_window
676
+ ret= WinUser.LockSetForegroundWindow(LSFW_LOCK)
677
+ ret != WIN_FALSE # todo: raise system error?
678
+ end
679
+ # The foreground process can call the #lock_set_foreground_window function to disable calls to
680
+ # the #set_foreground! function.
681
+ #
682
+ # Enables calls to #set_foreground!
683
+ #
684
+ # http://msdn.microsoft.com/en-us/library/ms633532%28VS.85%29.aspx
685
+ def self.unlock_set_foreground_window
686
+ ret= WinUser.LockSetForegroundWindow(LSFW_UNLOCK)
687
+ ret != WIN_FALSE # todo: raise system error?
688
+ end
689
+
690
+ # :stopdoc:
691
+ VK_MENU=0x12
692
+ KEYEVENTF_KEYDOWN=0x0
693
+ KEYEVENTF_KEYUP=0x2
694
+ # :startdoc:
695
+
696
+ # really sets this to be the foreground window.
697
+ #
698
+ # - restores the window if it's iconic.
699
+ # - attempts to circumvent a lock disabling calls made by set_foreground!
700
+ # - then calls set_foreground!, which should then work with that lock disabled.
701
+ # tries this for a few seconds, checking if it was successful.
702
+ #
703
+ # if you want it to raise an exception if it can't set the foreground window,
704
+ # pass :error => true (default is false)
705
+ def really_set_foreground!(options={})
706
+ options=handle_options(options, :error => false)
707
+ try_harder=false
708
+ mapped_vk_menu=WinUser.MapVirtualKeyA(VK_MENU, 0)
709
+ Waiter.try_for(2, :exception => (options[:error] && WinWindow::Error.new("Failed to set foreground window"))) do
710
+ if iconic?
711
+ restore!
712
+ end
713
+ if try_harder
714
+ # Simulate two single ALT keystrokes in order to deactivate lock on SetForeGroundWindow before we call it.
715
+ # See LockSetForegroundWindow, http://msdn.microsoft.com/en-us/library/ms633532(VS.85).aspx
716
+ # also keybd_event, see http://msdn.microsoft.com/en-us/library/ms646304(VS.85).aspx
717
+ #
718
+ # this idea is taken from AutoIt's setforegroundwinex.cpp in SetForegroundWinEx::Activate(HWND hWnd)
719
+ # keybd_event((BYTE)VK_MENU, MapVirtualKey(VK_MENU, 0), 0, 0);
720
+ # keybd_event((BYTE)VK_MENU, MapVirtualKey(VK_MENU, 0), KEYEVENTF_KEYUP, 0);
721
+ 2.times do
722
+ ret=WinUser.keybd_event(VK_MENU, mapped_vk_menu, KEYEVENTF_KEYDOWN, nil)
723
+ ret=WinUser.keybd_event(VK_MENU, mapped_vk_menu, KEYEVENTF_KEYUP, nil)
724
+ end
725
+ else
726
+ try_harder=true
727
+ end
728
+ set_foreground!
729
+ foreground?
730
+ end
731
+ end
732
+
733
+ # brings the window to the top of the Z order. If the window is a top-level window, it is
734
+ # activated. If the window is a child window, the top-level parent window associated with the
735
+ # child window is activated.
736
+ #
737
+ # http://msdn.microsoft.com/en-us/library/ms632673(VS.85).aspx
738
+ def bring_to_top!
739
+ ret=WinUser.BringWindowToTop(hwnd)
740
+ ret != WIN_FALSE
741
+ end
742
+
743
+ # minimizes this window (but does not destroy it)
744
+ # (why is it called close? I don't know)
745
+ #
746
+ # http://msdn.microsoft.com/en-us/library/ms632678(VS.85).aspx
747
+ def close!
748
+ ret=WinUser.CloseWindow(hwnd)
749
+ ret != WIN_FALSE
750
+ end
751
+
752
+ # Hides the window and activates another window.
753
+ #
754
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
755
+ def hide!
756
+ ret=WinUser.ShowWindow(hwnd, SW_HIDE)
757
+ ret != WIN_FALSE
758
+ end
759
+
760
+ # Activates and displays a window. If the window is minimized or maximized, the system restores
761
+ # it to its original size and position. An application should specify this flag when displaying
762
+ # the window for the first time.
763
+ #
764
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
765
+ def show_normal!
766
+ ret=WinUser.ShowWindow(hwnd, SW_SHOWNORMAL)
767
+ ret != WIN_FALSE
768
+ end
769
+
770
+ # Activates the window and displays it as a minimized window.
771
+ #
772
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
773
+ def show_minimized!
774
+ ret=WinUser.ShowWindow(hwnd, SW_SHOWMINIMIZED)
775
+ ret != WIN_FALSE
776
+ end
777
+
778
+ # Activates the window and displays it as a maximized window. (note: exact same as maximize!)
779
+ #
780
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
781
+ def show_maximized!
782
+ ret=WinUser.ShowWindow(hwnd, SW_SHOWMAXIMIZED)
783
+ ret != WIN_FALSE
784
+ end
785
+
786
+ # Maximizes this window. (note: exact same as show_maximized!)
787
+ #
788
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
789
+ def maximize!
790
+ ret=WinUser.ShowWindow(hwnd, SW_MAXIMIZE)
791
+ ret != WIN_FALSE
792
+ end
793
+
794
+ # Displays the window in its most recent size and position. This is similar to show_normal!,
795
+ # except the window is not actived.
796
+ #
797
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
798
+ def show_no_activate!
799
+ ret=WinUser.ShowWindow(hwnd, SW_SHOWNOACTIVATE)
800
+ ret != WIN_FALSE
801
+ end
802
+
803
+ # Activates the window and displays it in its current size and position.
804
+ #
805
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
806
+ def show!
807
+ ret=WinUser.ShowWindow(hwnd, SW_SHOW)
808
+ ret != WIN_FALSE
809
+ end
810
+
811
+ # Minimizes this window and activates the next top-level window in the Z order.
812
+ #
813
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
814
+ def minimize!
815
+ ret=WinUser.ShowWindow(hwnd, SW_MINIMIZE)
816
+ ret != WIN_FALSE
817
+ end
818
+
819
+ # Displays the window as a minimized window. This is similar to show_minimized!, except the
820
+ # window is not activated.
821
+ #
822
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
823
+ def show_min_no_active!
824
+ ret=WinUser.ShowWindow(hwnd, SW_SHOWMINNOACTIVE)
825
+ ret != WIN_FALSE
826
+ end
827
+
828
+ # Displays the window in its current size and position. This is similar to show!, except the
829
+ # window is not activated.
830
+ #
831
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
832
+ def show_na!
833
+ ret=WinUser.ShowWindow(hwnd, SW_SHOWNA)
834
+ ret != WIN_FALSE
835
+ end
836
+
837
+ # Activates and displays the window. If the window is minimized or maximized, the system
838
+ # restores it to its original size and position. An application should use this when restoring a
839
+ # minimized window.
840
+ #
841
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
842
+ def restore!
843
+ ret=WinUser.ShowWindow(hwnd, SW_RESTORE)
844
+ ret != WIN_FALSE
845
+ end
846
+
847
+ # Sets the show state based on the SW_ value specified in the STARTUPINFO structure passed to
848
+ # the CreateProcess function by the program that started the application.
849
+ #
850
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
851
+ def show_default!
852
+ ret=WinUser.ShowWindow(hwnd, SW_SHOWDEFAULT)
853
+ ret != WIN_FALSE
854
+ end
855
+
856
+ # Windows 2000/XP: Minimizes the window, even if the thread that owns the window is not
857
+ # responding. This should only be used when minimizing windows from a different thread.
858
+ #
859
+ # http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
860
+ def force_minimize!
861
+ ret=WinUser.ShowWindow(hwnd, SW_FORCEMINIMIZE)
862
+ ret != WIN_FALSE
863
+ end
864
+
865
+ # destroy! destroys the window. #destroy! sends WM_DESTROY and WM_NCDESTROY messages to the
866
+ # window to deactivate it and remove the keyboard focus from it. #destroy! also destroys the
867
+ # window's menu, flushes the thread message queue, destroys timers, removes clipboard ownership,
868
+ # and breaks the clipboard viewer chain (if the window is at the top of the viewer chain).
869
+ #
870
+ # If the specified window is a parent or owner window, #destroy! automatically destroys the
871
+ # associated child or owned windows when it destroys the parent or owner window. #destroy! first
872
+ # destroys child or owned windows, and then it destroys the parent or owner window.
873
+ # #destroy! also destroys modeless dialog boxes.
874
+ #
875
+ # http://msdn.microsoft.com/en-us/library/ms632682(VS.85).aspx
876
+ def destroy!
877
+ ret=WinUser.DestroyWindow(hwnd)
878
+ ret != WIN_FALSE
879
+ end
880
+
881
+ # called to forcibly close the window.
882
+ #
883
+ # the argument force, if true, will force the destruction of the window if an initial attempt
884
+ # fails to gently close the window using WM_CLOSE.
885
+ #
886
+ # if false, only the close with WM_CLOSE is attempted
887
+ #
888
+ # http://msdn.microsoft.com/en-us/library/ms633492(VS.85).aspx
889
+ def end_task!(force=false)
890
+ ret=WinUser.EndTask(hwnd, 0, force ? WIN_TRUE : WIN_FALSE)
891
+ ret != WIN_FALSE
892
+ end
893
+
894
+ # sends notification that the window should close.
895
+ # returns nil (we get no indication of success or failure).
896
+ #
897
+ # http://msdn.microsoft.com/en-us/library/ms632617%28VS.85%29.aspx
898
+ def send_close!
899
+ buff_size=0
900
+ buff=""
901
+ len=WinUser.SendMessageA(hwnd, WM_CLOSE, buff_size, buff)
902
+ nil
903
+ end
904
+
905
+ # tries to click on this Window (using PostMessage sending BM_CLICK message).
906
+ #
907
+ # Clicking might not always work! Especially if the window is not focused (frontmost
908
+ # application). The BM_CLICK message might just be ignored, or maybe it will just focus the hwnd
909
+ # but not really click.
910
+ def click!
911
+ WinUser.PostMessageA(hwnd, BM_CLICK, 0, nil)
912
+ end
913
+
914
+ # Returns a Rect struct with members left, top, right, and bottom indicating the dimensions of
915
+ # the bounding rectangle of the specified window. The dimensions are given in screen coordinates
916
+ # that are relative to the upper-left corner of the screen.
917
+ #
918
+ # http://msdn.microsoft.com/en-us/library/ms633519%28VS.85%29.aspx
919
+ def window_rect
920
+ rect=WinUser::Rect.new
921
+ ret=WinUser.GetWindowRect(hwnd, rect)
922
+ if ret==WIN_FALSE
923
+ self.class.system_error "GetWindowRect"
924
+ else
925
+ rect
926
+ end
927
+ end
928
+ # Returns a Rect struct with members left, top, right, and bottom indicating the coordinates
929
+ # of a window's client area. The client coordinates specify the upper-left and lower-right
930
+ # corners of the client area. Because client coordinates are relative to the upper-left corner
931
+ # of a window's client area, the coordinates of the upper-left corner are (0,0).
932
+ #
933
+ # http://msdn.microsoft.com/en-us/library/ms633503%28VS.85%29.aspx
934
+ def client_rect
935
+ rect=WinUser::Rect.new
936
+ ret=WinUser.GetClientRect(hwnd, rect)
937
+ if ret==WIN_FALSE
938
+ self.class.system_error "GetClientRect"
939
+ else
940
+ rect
941
+ end
942
+ end
943
+
944
+ # :stopdoc:
945
+ SRCCOPY = 0xCC0020
946
+ DIB_RGB_COLORS = 0x0
947
+ GMEM_FIXED = 0x0
948
+ # :startdoc:
949
+
950
+ # Creates a bitmap image of this window (a screenshot).
951
+ #
952
+ # Returns the bitmap as represented by three FFI objects: a BITMAPFILEHEADER, a BITMAPINFOHEADER,
953
+ # and a pointer to actual bitmap data.
954
+ #
955
+ # See also #capture_to_bmp_blob and #capture_to_bmp_file - probably more useful to the user than
956
+ # this method.
957
+ #
958
+ # takes an options hash:
959
+ # * :dc => what device context to use
960
+ # * :client - captures the client area, which excludes window trimmings like title bar, resize
961
+ # bars, etc.
962
+ # * :window (default) - capturse the window area, including window trimmings.
963
+ # * :set_foreground => whether to try to set this to be the foreground
964
+ # * true - calls to #set_foreground!
965
+ # * false - doesn't call to any functions to set this to be the foreground
966
+ # * :really (default) - calls to #really_set_foreground!. this is the default because really
967
+ # being in the foreground is rather important when taking a screenshot.
968
+ def capture_to_bmp_structs(options={})
969
+ options=handle_options(options, :dc => :window, :set_foreground => :really)
970
+ case options[:set_foreground]
971
+ when :really
972
+ really_set_foreground!
973
+ when true
974
+ set_foreground!
975
+ when false,nil
976
+ else
977
+ raise ArgumentError, ":set_foreground option is invalid. expected values are :really, true, or false/nil. received #{options[:set_foreground]} (#{options[:set_foreground].class})"
978
+ end
979
+ if options[:set_foreground]
980
+ # if setting foreground, sleep a tick - sometimes it still hasn't show up even when it is
981
+ # the foreground window; sometimes it's still only partway-drawn
982
+ sleep 0.2
983
+ end
984
+ case options[:dc]
985
+ when :client
986
+ rect=self.client_rect
987
+ dc=WinUser.GetDC(hwnd) || system_error("GetDC")
988
+ when :window
989
+ rect=self.window_rect
990
+ dc=WinUser.GetWindowDC(hwnd) || system_error("GetWindowDC")
991
+ else
992
+ raise ArgumentError, ":dc option is invalid. expected values are :client or :window; received #{options[:dc]} (#{options[:dc].class})"
993
+ end
994
+ width=rect[:right]-rect[:left]
995
+ height=rect[:bottom]-rect[:top]
996
+ begin
997
+ dc_mem = WinGDI.CreateCompatibleDC(dc) || system_error("CreateCompatibleDC")
998
+ begin
999
+ bmp = WinGDI.CreateCompatibleBitmap(dc, width, height) || system_error("CreateCompatibleBitmap")
1000
+ begin
1001
+ WinGDI.SelectObject(dc_mem, bmp) || system_error("SelectObject")
1002
+ WinGDI.BitBlt(dc_mem, 0, 0, width, height, dc, 0, 0, SRCCOPY) || system_error("BitBlt")
1003
+
1004
+ bytes_per_pixel=3
1005
+
1006
+ bmp_info=WinGDI::BITMAPINFOHEADER.new
1007
+ { :Size => WinGDI::BITMAPINFOHEADER.real_size, # 40
1008
+ :Width => width,
1009
+ :Height => height,
1010
+ :Planes => 1,
1011
+ :BitCount => bytes_per_pixel*8,
1012
+ :Compression => 0,
1013
+ :SizeImage => 0,
1014
+ :XPelsPerMeter => 0,
1015
+ :YPelsPerMeter => 0,
1016
+ :ClrUsed => 0,
1017
+ :ClrImportant => 0,
1018
+ }.each_pair do |key,val|
1019
+ bmp_info[key]=val
1020
+ end
1021
+ bmp_row_size=width*bytes_per_pixel
1022
+ bmp_row_size+=bmp_row_size%4 # row size must be a multiple of 4 (size of a dword)
1023
+ bmp_size=bmp_row_size*height
1024
+
1025
+ bits=FFI::MemoryPointer.new(1, bmp_size)
1026
+
1027
+ WinGDI.GetDIBits(dc_mem, bmp, 0, height, bits, bmp_info, DIB_RGB_COLORS) || system_error("GetDIBits")
1028
+
1029
+ bmp_file_header=WinGDI::BITMAPFILEHEADER.new
1030
+ { :Type => 'BM'.unpack('S').first, # must be 'BM'
1031
+ :Size => WinGDI::BITMAPFILEHEADER.real_size + WinGDI::BITMAPINFOHEADER.real_size + bmp_size,
1032
+ :Reserved1 => 0,
1033
+ :Reserved2 => 0,
1034
+ :OffBits => WinGDI::BITMAPFILEHEADER.real_size + WinGDI::BITMAPINFOHEADER.real_size
1035
+ }.each_pair do |key,val|
1036
+ bmp_file_header[key]=val
1037
+ end
1038
+ return [bmp_file_header, bmp_info, bits]
1039
+ ensure
1040
+ WinGDI.DeleteObject(bmp)
1041
+ end
1042
+ ensure
1043
+ WinGDI.DeleteDC(dc_mem)
1044
+ end
1045
+ ensure
1046
+ WinUser.ReleaseDC(hwnd, dc)
1047
+ end
1048
+ end
1049
+ # captures this window to a bitmap image (a screenshot).
1050
+ #
1051
+ # Returns the bitmap as represented by a blob (a string) of bitmap data, including the
1052
+ # BITMAPFILEHEADER, BITMAPINFOHEADER, and data. This can be written directly to a file (though if
1053
+ # you want that, #capture_to_bmp_file is probably what you want), or passed to ImageMagick, or
1054
+ # whatever you like.
1055
+ #
1056
+ # takes an options hash. see the documentation on #capture_to_bmp_structs for what options are
1057
+ # accepted.
1058
+ def capture_to_bmp_blob(options={})
1059
+ capture_to_bmp_structs(options).map do |struct|
1060
+ if struct.is_a?(FFI::Pointer)
1061
+ ptr=struct
1062
+ size=ptr.size
1063
+ else
1064
+ ptr=struct.to_ptr
1065
+ size=struct.class.real_size
1066
+ end
1067
+ ptr.get_bytes(0, size)
1068
+ end.join("")
1069
+ end
1070
+
1071
+ # captures this window to a bitmap image (a screenshot).
1072
+ #
1073
+ # stores the bitmap to a filename specified in the first argument.
1074
+ #
1075
+ # takes an options hash. see the documentation on #capture_to_bmp_structs for what options are
1076
+ # accepted.
1077
+ def capture_to_bmp_file(filename, options={})
1078
+ File.open(filename, 'wb') do |file|
1079
+ file.write(capture_to_bmp_blob(options))
1080
+ end
1081
+ end
1082
+
1083
+ private
1084
+ FORMAT_MESSAGE_FROM_SYSTEM=0x00001000 # :nodoc:
1085
+ # get the last error from GetLastError, format an error message with FormatMessage, and raise
1086
+ # a WinWindow::SystemError
1087
+ def self.system_error(function)
1088
+ code=WinKernel.GetLastError
1089
+ message = "#{function} encountered an error\nSystem Error Code #{code}"
1090
+ if AttachLib::CanTrustFormatMessage
1091
+ buff_size=65535
1092
+ buff="\1"*buff_size
1093
+ len=WinKernel.FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nil, code, 0, buff, buff_size)
1094
+ system_error_message="\n"+buff[0...len]
1095
+ message+="\n"+system_error_message
1096
+ end
1097
+ raise WinWindow::SystemError.new(message, :function => function, :code=> code, :system_message => system_error_message)
1098
+ end
1099
+ public
1100
+
1101
+ # iterates over each child, yielding a WinWindow object.
1102
+ #
1103
+ # raises a WinWindow::NotExistsError if the window does not exist, or a WinWindow::SystemError
1104
+ # if a System Error errors.
1105
+ #
1106
+ # use #children to get an Enumerable object.
1107
+ #
1108
+ # http://msdn.microsoft.com/en-us/library/ms633494(VS.85).aspx
1109
+ #
1110
+ # For System Error Codes see http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx
1111
+ def each_child
1112
+ raise WinWindow::NotExistsError, "Window does not exist! Cannot enumerate children." unless exists?
1113
+ enum_child_windows_callback= WinUser.window_enum_callback do |chwnd, lparam|
1114
+ yield WinWindow.new(chwnd)
1115
+ WIN_TRUE
1116
+ end
1117
+ begin
1118
+ ret=WinUser.EnumChildWindows(hwnd, enum_child_windows_callback, nil)
1119
+ ensure
1120
+ WinUser.remove_window_enum_callback(enum_child_windows_callback)
1121
+ end
1122
+ if ret==0
1123
+ self.class.system_error("EnumChildWindows")
1124
+ # actually, EnumChildWindows doesn't say anything about return value indicating error encountered.
1125
+ # Although EnumWindows does, so it seems sort of safe to assume that would apply here too.
1126
+ # but, maybe not - so, should we raise an error here?
1127
+ end
1128
+ nil
1129
+ end
1130
+
1131
+ # returns an Enumerable object that can iterate over each child of this window,
1132
+ # yielding a WinWindow object
1133
+ #
1134
+ # may raise a WinWindow::SystemError from #each_child
1135
+ def children
1136
+ Enumerator.new(self, :each_child)
1137
+ end
1138
+
1139
+ # iterates over each child recursively, yielding a WinWindow object for each child.
1140
+ #
1141
+ # raises a WinWindow::NotExistsError if the window does not exist, or a WinWindow::SystemError
1142
+ # if a System Error errors.
1143
+ #
1144
+ # use #children_recursive to get an Enumerable object.
1145
+ def recurse_each_child(options={})
1146
+ options=handle_options(options, :rescue_enum_child_windows => true)
1147
+ ycomb do |recurse|
1148
+ proc do |win_window|
1149
+ yield win_window
1150
+ begin
1151
+ win_window.each_child do |child_window|
1152
+ recurse.call child_window
1153
+ end
1154
+ rescue SystemError
1155
+ raise unless options[:rescue_enum_child_windows] && $!.function=='EnumChildWindows'
1156
+ end
1157
+ end
1158
+ end.call(self)
1159
+ end
1160
+ # returns an Enumerable object that can iterate over each child of this window recursively,
1161
+ # yielding a WinWindow object for each child.
1162
+ def children_recursive(options={})
1163
+ Enumerator.new(self, :recurse_each_child, options)
1164
+ end
1165
+
1166
+ # true if comparing an object of the same class with the same hwnd (integer)
1167
+ def eql?(oth)
1168
+ oth.class==self.class && oth.hwnd==self.hwnd
1169
+ end
1170
+ alias == eql?
1171
+
1172
+ def hash # :nodoc:
1173
+ [self.class, self.hwnd].hash
1174
+ end
1175
+
1176
+
1177
+
1178
+ # more specialized methods
1179
+
1180
+ # Give the name of a button, or a Regexp to match it (see #child_button).
1181
+ # keeps clicking the button until the button no longer exists, or until
1182
+ # the given block is true (ie, not false or nil)
1183
+ #
1184
+ # Options:
1185
+ # * :interval is the length of time in seconds between each attempt (default 0.05)
1186
+ # * :set_foreground is whether the window should be activated first, since button-clicking is
1187
+ # much more likely to fail if the window isn't focused (default true)
1188
+ # * :exception is the exception class or instance that will be raised if we can't click the
1189
+ # button (default nil, no exception is raised, the return value indicates success/failure)
1190
+ #
1191
+ # Raises ArgumentError if invalid options are given.
1192
+ # Raises a WinWindow::NotExistsError if the button doesn't exist, or if this window doesn't
1193
+ # exist, or a WinWindow::SystemError if a System Error occurs (from #each_child)
1194
+ def click_child_button_try_for!(button_text, time, options={})
1195
+ options=handle_options(options, {:set_foreground => true, :exception => nil, :interval => 0.05})
1196
+ button=child_button(button_text) || (raise WinWindow::NotExistsError, "Button #{button_text.inspect} not found")
1197
+ waiter_options={}
1198
+ waiter_options[:condition]=proc{!button.exists? || (block_given? && yield)}
1199
+ waiter_options.merge!(options.reject{|k,v| ![:exception, :interval].include?(k)})
1200
+ Waiter.try_for(time, waiter_options) do
1201
+ if options[:set_foreground]
1202
+ show_normal!
1203
+ really_set_foreground!
1204
+ end
1205
+ button.click!
1206
+ end
1207
+ return waiter_options[:condition].call
1208
+ end
1209
+
1210
+ # returns a WinWindow that is a child of this that matches the given button_text (Regexp or
1211
+ # #to_s-able) or nil if no such child exists.
1212
+ #
1213
+ # "&" is stripped when matching so don't include it. String comparison is case-insensitive.
1214
+ #
1215
+ # May raise a WinWindow::SystemError from #each_child
1216
+ def child_button(button_text)
1217
+ children.detect do |child|
1218
+ child.class_name=='Button' && button_text.is_a?(Regexp) ? child.text.tr('&', '') =~ button_text : child.text.tr('&', '').downcase==button_text.to_s.tr('&', '').downcase
1219
+ end
1220
+ end
1221
+
1222
+ # Finds a child of this window which follows a label with the given text.
1223
+ #
1224
+ # Options:
1225
+ # - :control_class_name is the class name of the control you are looking for. Defaults to nil,
1226
+ # which accepts any class name.
1227
+ # - :label_class_name is the class name of the label preceding the control you are looking for.
1228
+ # Defaults to 'Static'
1229
+ def child_control_with_preceding_label(preceding_label_text, options={})
1230
+ options=handle_options(options, :control_class_name => nil, :label_class_name => "Static")
1231
+
1232
+ prev_was_label=false
1233
+ control=self.children.detect do |child|
1234
+ ret=prev_was_label && (!options[:control_class_name] || child.class_name==options[:control_class_name])
1235
+ prev_was_label= child.class_name==options[:label_class_name] && preceding_label_text===child.text
1236
+ ret
1237
+ end
1238
+ end
1239
+
1240
+
1241
+
1242
+ # -- Class methods:
1243
+
1244
+ # Iterates over every window yielding a WinWindow object.
1245
+ #
1246
+ # use WinWindow::All if you want an Enumerable object.
1247
+ #
1248
+ # Raises a WinWindow::SystemError if a System Error occurs.
1249
+ #
1250
+ # http://msdn.microsoft.com/en-us/library/ms633497.aspx
1251
+ #
1252
+ # For System Error Codes see http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx
1253
+ def self.each_window # :yields: win_window
1254
+ enum_windows_callback= WinUser.window_enum_callback do |hwnd,lparam|
1255
+ yield WinWindow.new(hwnd)
1256
+ WIN_TRUE
1257
+ end
1258
+ begin
1259
+ ret=WinUser.EnumWindows(enum_windows_callback, nil)
1260
+ ensure
1261
+ WinUser.remove_window_enum_callback(enum_windows_callback)
1262
+ end
1263
+ if ret==WIN_FALSE
1264
+ system_error "EnumWindows"
1265
+ end
1266
+ nil
1267
+ end
1268
+
1269
+ # Enumerable object that iterates over every available window
1270
+ #
1271
+ # May raise a WinWindow::SystemError from WinWindow.each_window
1272
+ All = Enumerator.new(WinWindow, :each_window)
1273
+
1274
+ # returns the first window found whose text matches what is given
1275
+ #
1276
+ # May raise a WinWindow::SystemError from WinWindow.each_window
1277
+ def self.find_first_by_text(text)
1278
+ WinWindow::All.detect do |window|
1279
+ text===window.text # use triple-equals so regexps try to match, strings see if equal
1280
+ end
1281
+ end
1282
+
1283
+ # returns all WinWindow objects found whose text matches what is given
1284
+ #
1285
+ # May raise a WinWindow::SystemError from WinWindow.each_window
1286
+ def self.find_all_by_text(text)
1287
+ WinWindow::All.select do |window|
1288
+ text===window.text # use triple-equals so regexps try to match, strings see if equal
1289
+ end
1290
+ end
1291
+
1292
+ # returns the only window matching the given text.
1293
+ # raises a WinWindow::MatchError if more than one window matching given text is found,
1294
+ # so that you can be sure you are returned the right one (because it's the only one)
1295
+ #
1296
+ # behavior is slightly more complex than that - if multiple windows match the given
1297
+ # text, but are all in one heirarchy (all parents/children of each other), then this
1298
+ # returns the highest one in the heirarchy.
1299
+ #
1300
+ # if there are multiple windows with titles that match which are all in a parent/child
1301
+ # relationship with each other, this will not error and will return the innermost child
1302
+ # whose text matches.
1303
+ #
1304
+ # May also raise a WinWindow::SystemError from WinWindow.each_window
1305
+ def self.find_only_by_text(text)
1306
+ matched=WinWindow::All.select do |window|
1307
+ text===window.text
1308
+ end
1309
+ # matched.reject! do |win| # reject win where
1310
+ # matched.any? do |other_win| # exists any other_win
1311
+ # parent=other_win.parent
1312
+ # win_is_parent=false
1313
+ # while parent && !win_is_parent
1314
+ # win_is_parent ||= win==parent
1315
+ # parent=parent.parent
1316
+ # end
1317
+ # win_is_parent # such that win is parent of other_win
1318
+ # end
1319
+ # end
1320
+ matched.reject! do |win| # reject any win where
1321
+ matched.any? do |other_win| # any other_win
1322
+ parent=win.parent
1323
+ other_is_parent=false
1324
+ while parent && !other_is_parent
1325
+ other_is_parent ||= other_win==parent
1326
+ parent=parent.parent
1327
+ end
1328
+ other_is_parent # is its parent
1329
+ end
1330
+ end
1331
+
1332
+ if matched.size != 1
1333
+ raise MatchError, "Found #{matched.size} windows matching #{text.inspect}; there should be one"
1334
+ else
1335
+ return matched.first
1336
+ end
1337
+ end
1338
+
1339
+ # Returns a WinWindow representing the current foreground window (the window with which the user
1340
+ # is currently working).
1341
+ #
1342
+ # http://msdn.microsoft.com/en-us/library/ms633505%28VS.85%29.aspx
1343
+ def self.foreground_window
1344
+ hwnd=WinUser.GetForegroundWindow
1345
+ if hwnd == 0
1346
+ nil
1347
+ else
1348
+ self.new(hwnd)
1349
+ end
1350
+ end
1351
+
1352
+ # Returns a WinWindow representing the desktop window. The desktop window covers the entire
1353
+ # screen. The desktop window is the area on top of which other windows are painted.
1354
+ #
1355
+ # http://msdn.microsoft.com/en-us/library/ms633504%28VS.85%29.aspx
1356
+ def self.desktop_window
1357
+ hwnd=WinUser.GetDesktopWindow
1358
+ if hwnd == 0
1359
+ nil
1360
+ else
1361
+ self.new(hwnd)
1362
+ end
1363
+ end
1364
+ end