win 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +12 -13
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/lib/win/dde.rb +223 -0
- data/lib/win/library.rb +162 -151
- data/lib/win/window/extra.rb +113 -0
- data/lib/win/window.rb +280 -222
- data/spec/spec_helper.rb +15 -7
- data/spec/win/dde_spec.rb +68 -0
- data/spec/win/window/extra_spec.rb +181 -0
- data/spec/win/window_spec.rb +232 -5
- data/win.gemspec +13 -4
- metadata +20 -4
data/lib/win/window.rb
CHANGED
@@ -5,6 +5,12 @@ module Win
|
|
5
5
|
# Contains constants, functions and wrappers related to Windows manipulation
|
6
6
|
#
|
7
7
|
module Window
|
8
|
+
# Internal constants:
|
9
|
+
|
10
|
+
# Windows keyboard-related Constants:
|
11
|
+
# ? move to keyboard.rb?
|
12
|
+
KEY_DELAY = 0.00001
|
13
|
+
|
8
14
|
include Win::Library
|
9
15
|
|
10
16
|
#General constants:
|
@@ -16,6 +22,70 @@ module Win
|
|
16
22
|
# Sys Command Close
|
17
23
|
SC_CLOSE = 0xF060
|
18
24
|
|
25
|
+
# Key down keyboard event (the key is being depressed)
|
26
|
+
KEYEVENTF_KEYDOWN = 0
|
27
|
+
# Key up keyboard event (the key is being released)
|
28
|
+
KEYEVENTF_KEYUP = 2
|
29
|
+
# Extended kb event. If specified, the scan code was preceded by a prefix byte having the value 0xE0 (224).
|
30
|
+
KEYEVENTF_EXTENDEDKEY = 1
|
31
|
+
|
32
|
+
# Virtual key codes:
|
33
|
+
|
34
|
+
# Control-break processing
|
35
|
+
VK_CANCEL = 0x03
|
36
|
+
# Backspace? key
|
37
|
+
VK_BACK = 0x08
|
38
|
+
# Tab key
|
39
|
+
VK_TAB = 0x09
|
40
|
+
# Shift key
|
41
|
+
VK_SHIFT = 0x10
|
42
|
+
# Ctrl key
|
43
|
+
VK_CONTROL = 0x11
|
44
|
+
# ENTER key
|
45
|
+
VK_RETURN = 0x0D
|
46
|
+
# ALT key
|
47
|
+
VK_ALT = 0x12
|
48
|
+
# ALT key alias
|
49
|
+
VK_MENU = 0x12
|
50
|
+
# PAUSE key
|
51
|
+
VK_PAUSE = 0x13
|
52
|
+
# CAPS LOCK key
|
53
|
+
VK_CAPITAL = 0x14
|
54
|
+
# ESC key
|
55
|
+
VK_ESCAPE = 0x1B
|
56
|
+
# SPACEBAR
|
57
|
+
VK_SPACE = 0x20
|
58
|
+
# PAGE UP key
|
59
|
+
VK_PRIOR = 0x21
|
60
|
+
# PAGE DOWN key
|
61
|
+
VK_NEXT = 0x22
|
62
|
+
# END key
|
63
|
+
VK_END = 0x23
|
64
|
+
# HOME key
|
65
|
+
VK_HOME = 0x24
|
66
|
+
# LEFT ARROW key
|
67
|
+
VK_LEFT = 0x25
|
68
|
+
# UP ARROW key
|
69
|
+
VK_UP = 0x26
|
70
|
+
# RIGHT ARROW key
|
71
|
+
VK_RIGHT = 0x27
|
72
|
+
# DOWN ARROW key
|
73
|
+
VK_DOWN = 0x28
|
74
|
+
# SELECT key
|
75
|
+
VK_SELECT = 0x29
|
76
|
+
# PRINT key
|
77
|
+
VK_PRINT = 0x2A
|
78
|
+
# EXECUTE key
|
79
|
+
VK_EXECUTE = 0x2B
|
80
|
+
# PRINT SCREEN key
|
81
|
+
VK_SNAPSHOT = 0x2C
|
82
|
+
# INS key
|
83
|
+
VK_INSERT = 0x2D
|
84
|
+
# DEL key
|
85
|
+
VK_DELETE = 0x2E
|
86
|
+
# HELP key
|
87
|
+
VK_HELP = 0x2F
|
88
|
+
|
19
89
|
# ShowWindow constants:
|
20
90
|
|
21
91
|
# Hides the window and activates another window.
|
@@ -51,6 +121,50 @@ module Win
|
|
51
121
|
# flag when minimizing windows from a different thread.
|
52
122
|
SW_FORCEMINIMIZE = 11
|
53
123
|
|
124
|
+
class << self
|
125
|
+
# Def_block that calls API function expecting EnumWindowsProc callback (EnumWindows, EnumChildWindows, ...).
|
126
|
+
# Default callback just pushes all passed handles into Array that is returned if Enum function call was successful.
|
127
|
+
# If runtime block is given it is added to the end of default callback (handles Array is still collected/returned).
|
128
|
+
# If Enum function call failed, method returns nil, otherwise an Array of window handles.
|
129
|
+
#
|
130
|
+
def return_enum #:nodoc:
|
131
|
+
lambda do |api, *args, &block|
|
132
|
+
args.push 0 if args.size == api.prototype.size - 2 # If value is missing, it defaults to 0
|
133
|
+
handles = []
|
134
|
+
|
135
|
+
# Insert callback proc into appropriate place of args Array
|
136
|
+
args[api.prototype.find_index(:enum_callback), 0] =
|
137
|
+
proc do |handle, message|
|
138
|
+
handles << handle
|
139
|
+
block ? block[handle, message] : true
|
140
|
+
end
|
141
|
+
handles if api.call *args
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Helper method that creates def_block returning (possibly encoded) string as a result of
|
146
|
+
# api function call or nil if zero characters was returned by api call
|
147
|
+
#
|
148
|
+
def return_string( encode = nil ) #:nodoc:
|
149
|
+
lambda do |api, *args|
|
150
|
+
namespace.enforce_count( args, api.prototype, -2)
|
151
|
+
buffer = FFI::MemoryPointer.new :char, 1024
|
152
|
+
buffer.put_string(0, "\x00" * 1023)
|
153
|
+
args += [buffer, 1024]
|
154
|
+
num_chars = api.call(*args)
|
155
|
+
return nil if num_chars == 0
|
156
|
+
if encode
|
157
|
+
string = buffer.get_bytes(0, num_chars*2)
|
158
|
+
string = string.force_encoding('utf-16LE').encode(encode)
|
159
|
+
else
|
160
|
+
string = buffer.get_bytes(0, num_chars)
|
161
|
+
end
|
162
|
+
string.rstrip
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private :return_enum, :return_string
|
167
|
+
end
|
54
168
|
|
55
169
|
# Windows GUI API definitions:
|
56
170
|
|
@@ -71,7 +185,7 @@ module Win
|
|
71
185
|
# WS_VISIBLE style, it may be true even if the window is totally obscured by other windows.
|
72
186
|
#
|
73
187
|
# :call-seq:
|
74
|
-
# visible?( win_handle )
|
188
|
+
# [window_]visible?( win_handle )
|
75
189
|
#
|
76
190
|
function 'IsWindowVisible', 'L', 'L', aliases: :visible?
|
77
191
|
|
@@ -148,20 +262,6 @@ module Win
|
|
148
262
|
#
|
149
263
|
function 'FindWindowEx', 'LLPP', 'L', zeronil: true
|
150
264
|
|
151
|
-
# Helper method that creates def_block returning (possibly encoded) string as a result of
|
152
|
-
# api function call or nil if zero characters was returned by api call
|
153
|
-
# TODO: should be private
|
154
|
-
def self.return_string( encode = nil ) #:nodoc:
|
155
|
-
lambda do |api, *args|
|
156
|
-
namespace.enforce_count( args, api.prototype, -2)
|
157
|
-
args += [string = buffer, string.length]
|
158
|
-
num_chars = api.call(*args)
|
159
|
-
return nil if num_chars == 0
|
160
|
-
string = string.force_encoding('utf-16LE').encode(encode) if encode
|
161
|
-
string.rstrip
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
265
|
##
|
166
266
|
# Returns the text of the specified window's title bar (if it has one).
|
167
267
|
# If the specified window is a control, the text of the control is copied. However, GetWindowText
|
@@ -195,7 +295,7 @@ module Win
|
|
195
295
|
# of calling GetWindowText.
|
196
296
|
#
|
197
297
|
#:call-seq:
|
198
|
-
# text =
|
298
|
+
# text = [get_]window_text( win_handle )
|
199
299
|
#
|
200
300
|
function 'GetWindowText', 'LPI', 'L', &return_string
|
201
301
|
|
@@ -204,7 +304,7 @@ module Win
|
|
204
304
|
# API improved to require only win_handle and return rstripped string
|
205
305
|
#
|
206
306
|
#:call-seq:
|
207
|
-
# text =
|
307
|
+
# text = [get_]window_text_w( win_handle )
|
208
308
|
#
|
209
309
|
function 'GetWindowTextW', 'LPI', 'L', &return_string('utf-8')
|
210
310
|
|
@@ -227,7 +327,7 @@ module Win
|
|
227
327
|
# Returns: Name of the class or NIL if function fails. For extended error information, call GetLastError.
|
228
328
|
#
|
229
329
|
#:call-seq:
|
230
|
-
# text =
|
330
|
+
# text = [get_]class_name( win_handle )
|
231
331
|
#
|
232
332
|
function 'GetClassName', 'LPI', 'I', &return_string
|
233
333
|
|
@@ -236,7 +336,7 @@ module Win
|
|
236
336
|
# API improved to require only win_handle and return rstripped string
|
237
337
|
#
|
238
338
|
#:call-seq:
|
239
|
-
# text =
|
339
|
+
# text = [get_]class_name_w( win_handle )
|
240
340
|
#
|
241
341
|
function 'GetClassNameW', 'LPI', 'I', &return_string('utf-8')
|
242
342
|
|
@@ -265,12 +365,6 @@ module Win
|
|
265
365
|
show_window(win_handle, SW_HIDE)
|
266
366
|
end
|
267
367
|
|
268
|
-
return_thread_process = lambda do |api, *args|
|
269
|
-
namespace.enforce_count( args, api.prototype, -1)
|
270
|
-
thread = api.call(args.first, process = [1].pack('L'))
|
271
|
-
nonzero_array(thread, *process.unpack('L'))
|
272
|
-
end
|
273
|
-
|
274
368
|
##
|
275
369
|
# Retrieves the identifier of the thread that created the specified window
|
276
370
|
# and, optionally, the identifier of the process that created the window.
|
@@ -287,16 +381,15 @@ module Win
|
|
287
381
|
# Returns: Pair of identifiers of the thread and process_id that created the window.
|
288
382
|
#
|
289
383
|
#:call-seq:
|
290
|
-
# thread, process_id =
|
384
|
+
# thread, process_id = [get_]window_tread_process_id( win_handle )
|
291
385
|
#
|
292
|
-
function 'GetWindowThreadProcessId',
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
end
|
386
|
+
function 'GetWindowThreadProcessId', [:long, :pointer], :long, &->(api, *args) {
|
387
|
+
namespace.enforce_count( args, api.prototype, -1)
|
388
|
+
process = FFI::MemoryPointer.new(:long)
|
389
|
+
process.write_long(1)
|
390
|
+
thread = api.call(args.first, process)
|
391
|
+
thread == 0 ? [nil, nil] : [thread, process.read_long()] }
|
392
|
+
# weird lambda literal instead of block is needed because RDoc goes crazy if block is attached to meta-definition
|
300
393
|
|
301
394
|
##
|
302
395
|
# Retrieves the dimensions of the specified window bounding rectangle.
|
@@ -318,91 +411,128 @@ module Win
|
|
318
411
|
# are exclusive. In other words, the pixel at (right, bottom) lies immediately outside the rectangle.
|
319
412
|
#
|
320
413
|
#:call-seq:
|
321
|
-
# rect =
|
322
|
-
#
|
323
|
-
function 'GetWindowRect', 'LP', 'I',
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
#
|
332
|
-
#
|
333
|
-
#
|
334
|
-
#
|
335
|
-
#
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
#
|
341
|
-
#
|
342
|
-
#
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
#
|
347
|
-
#
|
348
|
-
#
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
#
|
378
|
-
#
|
379
|
-
#
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
#
|
403
|
-
|
404
|
-
|
405
|
-
#
|
414
|
+
# rect = [get_]window_rect( win_handle )
|
415
|
+
#
|
416
|
+
function 'GetWindowRect', 'LP', 'I', &->(api, *args) {
|
417
|
+
namespace.enforce_count( args, api.prototype, -1)
|
418
|
+
rect = FFI::MemoryPointer.new(:long, 4)
|
419
|
+
rect.write_array_of_long([0, 0, 0, 0])
|
420
|
+
res = api.call args.first, rect
|
421
|
+
res == 0 ? [nil, nil, nil, nil] : rect.read_array_of_long(4) }
|
422
|
+
# weird lambda literal instead of block is needed because RDoc goes crazy if block is attached to meta-definition
|
423
|
+
|
424
|
+
# This is an application-defined callback function that receives top-level window handles as a result of a call
|
425
|
+
# to the EnumWindows, EnumChildWindows or EnumDesktopWindows function.
|
426
|
+
#
|
427
|
+
# Syntax: BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam );
|
428
|
+
#
|
429
|
+
# Parameters:
|
430
|
+
# hwnd (L) - [out] Handle to a top-level window.
|
431
|
+
# lParam (L) - [in] Specifies the application-defined value given in EnumWindows or EnumDesktopWindows.
|
432
|
+
# Return Values:
|
433
|
+
# TRUE continues enumeration. FALSE stops enumeration.
|
434
|
+
#
|
435
|
+
# Remarks: An application must register this callback function by passing its address to EnumWindows or EnumDesktopWindows.
|
436
|
+
callback :enum_callback, 'LL', :bool
|
437
|
+
|
438
|
+
##
|
439
|
+
# The EnumWindows function enumerates all top-level windows on the screen by passing the handle to
|
440
|
+
# each window, in turn, to an application-defined callback function. EnumWindows continues until
|
441
|
+
# the last top-level window is enumerated or the callback function returns FALSE.
|
442
|
+
#
|
443
|
+
# Original Parameters:
|
444
|
+
# callback [K] - Pointer to an application-defined callback function (see EnumWindowsProc).
|
445
|
+
# value [P] - Specifies an application-defined value(message) to be passed to the callback function.
|
446
|
+
# Original Return: Nonzero if the function succeeds, zero if the function fails. GetLastError for error info.
|
447
|
+
# If callback returns zero, the return value is also zero. In this case, the callback function should
|
448
|
+
# call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows.
|
449
|
+
#
|
450
|
+
# API improved to accept blocks (instead of callback objects) and message as a single arg
|
451
|
+
#
|
452
|
+
# New Parameters:
|
453
|
+
# message [P] - Specifies an application-defined value(message) to be passed to the callback function.
|
454
|
+
# block given to method invocation serves as an application-defined callback function (see EnumWindowsProc).
|
455
|
+
# Returns: True if the function succeeds, false if the function fails. GetLastError for error info.
|
456
|
+
# If callback returns zero, the return value is also zero. In this case, the callback function should
|
457
|
+
# call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows.
|
458
|
+
#
|
459
|
+
# Remarks: The EnumWindows function does not enumerate child windows, with the exception of a few top-level
|
460
|
+
# windows owned by the system that have the WS_CHILD style. This function is more reliable than calling
|
461
|
+
# the GetWindow function in a loop. An application that calls GetWindow to perform this task risks being
|
462
|
+
# caught in an infinite loop or referencing a handle to a window that has been destroyed.
|
463
|
+
#
|
464
|
+
#:call-seq:
|
465
|
+
# handles = enum_windows( [value] ) {|handle, message| your callback procedure }
|
466
|
+
#
|
467
|
+
function'EnumWindows', [:enum_callback, :long], :bool, &return_enum
|
468
|
+
|
469
|
+
##
|
470
|
+
#EnumDesktopWindows Function
|
471
|
+
#
|
472
|
+
#Enumerates all top-level windows associated with the specified desktop. It passes the handle to each window, in turn, to an application-defined callback function.
|
473
|
+
#
|
474
|
+
#
|
475
|
+
#Syntax
|
476
|
+
#BOOL WINAPI EnumDesktopWindows(
|
477
|
+
# __in_opt HDESK hDesktop,
|
478
|
+
# __in WNDENUMPROC lpfn,
|
479
|
+
# __in LPARAM lParam
|
480
|
+
#);
|
481
|
+
#
|
482
|
+
#Parameters
|
483
|
+
#hDesktop
|
484
|
+
#A handle to the desktop whose top-level windows are to be enumerated. This handle is returned by the CreateDesktop, GetThreadDesktop, OpenDesktop, or OpenInputDesktop function, and must have the DESKTOP_ENUMERATE access right. For more information, see Desktop Security and Access Rights.
|
485
|
+
#
|
486
|
+
#If this parameter is NULL, the current desktop is used.
|
487
|
+
#
|
488
|
+
#lpfn
|
489
|
+
#A pointer to an application-defined EnumWindowsProc callback function.
|
490
|
+
#
|
491
|
+
#lParam
|
492
|
+
#An application-defined value to be passed to the callback function.
|
493
|
+
#
|
494
|
+
#Return Value
|
495
|
+
#If the function fails or is unable to perform the enumeration, the return value is zero.
|
496
|
+
#
|
497
|
+
#To get extended error information, call GetLastError.
|
498
|
+
#
|
499
|
+
#You must ensure that the callback function sets SetLastError if it fails.
|
500
|
+
#
|
501
|
+
#Windows Server 2003 and Windows XP/2000: If there are no windows on the desktop, GetLastError returns ERROR_INVALID_HANDLE.
|
502
|
+
#Remarks
|
503
|
+
#The EnumDesktopWindows function repeatedly invokes the lpfn callback function until the last top-level window is enumerated or the callback function returns FALSE.
|
504
|
+
#
|
505
|
+
#Requirements
|
506
|
+
#Client Requires Windows Vista, Windows XP, or Windows 2000 Professional.
|
507
|
+
function'EnumDesktopWindows', [:ulong, :enum_callback, :long], :bool, &return_enum
|
508
|
+
|
509
|
+
##
|
510
|
+
# Enumerates child windows to a given window.
|
511
|
+
#
|
512
|
+
# Original Parameters:
|
513
|
+
# parent (L) - Handle to the parent window whose child windows are to be enumerated.
|
514
|
+
# callback [K] - Pointer to an application-defined callback function (see EnumWindowsProc).
|
515
|
+
# message [P] - Specifies an application-defined value(message) to be passed to the callback function.
|
516
|
+
# Original Return: Not used (?!)
|
517
|
+
# If callback returns zero, the return value is also zero. In this case, the callback function should
|
518
|
+
# call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows.
|
519
|
+
# If it is nil, this function is equivalent to EnumWindows. Windows 95/98/Me: parent cannot be NULL.
|
520
|
+
#
|
521
|
+
# API improved to accept blocks (instead of callback objects) and parent handle (value is optional, default 0)
|
522
|
+
# New Parameters:
|
523
|
+
# parent (L) - Handle to the parent window whose child windows are to be enumerated.
|
524
|
+
# value (P) - Specifies an application-defined value(message) to be passed to the callback function.
|
525
|
+
# block given to method invocation serves as an application-defined callback function (see EnumChildProc).
|
526
|
+
#
|
527
|
+
# Remarks:
|
528
|
+
# If a child window has created child windows of its own, EnumChildWindows enumerates those windows as well.
|
529
|
+
# A child window that is moved or repositioned in the Z order during the enumeration process will be properly enumerated.
|
530
|
+
# The function does not enumerate a child window that is destroyed before being enumerated or that is created during the enumeration process.
|
531
|
+
#
|
532
|
+
#:call-seq:
|
533
|
+
# handles = enum_child_windows( parent_handle, [value = 0] ) {|handle, message| your callback procedure }
|
534
|
+
#
|
535
|
+
function 'EnumChildWindows', [:ulong, :enum_callback, :long], :bool, &return_enum
|
406
536
|
|
407
537
|
##
|
408
538
|
# GetForegroundWindow function returns a handle to the foreground window (the window with which the user
|
@@ -415,10 +545,13 @@ module Win
|
|
415
545
|
# certain circumstances, such as when a window is losing activation.
|
416
546
|
#
|
417
547
|
#:call-seq:
|
418
|
-
# win_handle =
|
548
|
+
# win_handle = [get_]foreground_window()
|
419
549
|
#
|
420
550
|
function 'GetForegroundWindow', [], 'L'
|
421
551
|
|
552
|
+
##
|
553
|
+
# Tests if given window handle points to foreground (topmost) window
|
554
|
+
#
|
422
555
|
def foreground?(win_handle)
|
423
556
|
win_handle == foreground_window
|
424
557
|
end
|
@@ -436,115 +569,40 @@ module Win
|
|
436
569
|
# To get the window handle to the active window in the message queue for another thread, use GetGUIThreadInfo.
|
437
570
|
#
|
438
571
|
#:call-seq:
|
439
|
-
# win_handle =
|
572
|
+
# win_handle = [get_]active_window()
|
440
573
|
#
|
441
574
|
function 'GetActiveWindow', [], 'L'
|
442
575
|
|
576
|
+
##
|
577
|
+
# The keybd_event function synthesizes a keystroke. The system can use such a synthesized keystroke to generate
|
578
|
+
# a WM_KEYUP or WM_KEYDOWN message. The keyboard driver's interrupt handler calls the keybd_event function.
|
579
|
+
#
|
580
|
+
# !!!! Windows NT/2000/XP/Vista:This function has been superseded. Use SendInput instead.
|
581
|
+
#
|
582
|
+
# Syntax: VOID keybd_event( BYTE bVk, BYTE bScan, DWORD dwFlags, PTR dwExtraInfo);
|
583
|
+
#
|
584
|
+
# Parameters:
|
585
|
+
# bVk [C] - [in] Specifies a virtual-key code. The code must be a value in the range 1 to 254.
|
586
|
+
# For a complete list, see Virtual-Key Codes.
|
587
|
+
# bScan [C] - [in] Specifies a hardware scan code for the key.
|
588
|
+
# dwFlags [L] - [in] Specifies various aspects of function operation. This parameter can be
|
589
|
+
# one or more of the following values:
|
590
|
+
# KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, KEYEVENTF_KEYDOWN
|
591
|
+
# dwExtraInfo [L] -[in] Specifies an additional value associated with the key stroke.
|
592
|
+
#
|
593
|
+
# Return Value: none
|
594
|
+
#
|
595
|
+
# Remarks: An application can simulate a press of the PRINTSCRN key in order to obtain a screen snapshot and save
|
596
|
+
# it to the clipboard. To do this, call keybd_event with the bVk parameter set to VK_SNAPSHOT.
|
597
|
+
#
|
598
|
+
# Windows NT/2000/XP: The keybd_event function can toggle the NUM LOCK, CAPS LOCK, and SCROLL LOCK keys.
|
599
|
+
# Windows 95/98/Me: The keybd_event function can toggle only the CAPS LOCK and SCROLL LOCK keys.
|
600
|
+
#
|
443
601
|
function 'keybd_event', 'IILL', 'V'
|
602
|
+
|
444
603
|
function 'PostMessage', 'LLLL', 'L'
|
445
604
|
function 'SendMessage', 'LLLP', 'L'
|
446
605
|
function 'GetDlgItem', 'LL', 'L'
|
447
606
|
|
448
|
-
|
449
|
-
# Convenience wrapper methods:
|
450
|
-
|
451
|
-
# emulates combinations of keys pressed (Ctrl+Alt+P+M, etc)
|
452
|
-
def keystroke(*keys)
|
453
|
-
return if keys.empty?
|
454
|
-
keybd_event keys.first, 0, KEYEVENTF_KEYDOWN, 0
|
455
|
-
sleep WG_KEY_DELAY
|
456
|
-
keystroke *keys[1..-1]
|
457
|
-
sleep WG_KEY_DELAY
|
458
|
-
keybd_event keys.first, 0, KEYEVENTF_KEYUP, 0
|
459
|
-
end
|
460
|
-
|
461
|
-
# types text message into window holding the focus
|
462
|
-
def type_in(message)
|
463
|
-
message.scan(/./m) do |char|
|
464
|
-
keystroke(*char.to_vkeys)
|
465
|
-
end
|
466
|
-
end
|
467
|
-
|
468
|
-
# finds top-level dialog window by title and yields it to given block
|
469
|
-
def dialog(title, seconds=3)
|
470
|
-
d = begin
|
471
|
-
win = Window.top_level(title, seconds)
|
472
|
-
yield(win) ? win : nil
|
473
|
-
rescue TimeoutError
|
474
|
-
end
|
475
|
-
d.wait_for_close if d
|
476
|
-
return d
|
477
|
-
end
|
478
|
-
|
479
|
-
# Thin wrapper around window handle
|
480
|
-
class Window
|
481
|
-
include Win::Window
|
482
|
-
extend Win::Window
|
483
|
-
|
484
|
-
attr_reader :handle
|
485
|
-
|
486
|
-
# find top level window by title, return wrapped Window object
|
487
|
-
def self.top_level(title, seconds=3)
|
488
|
-
@handle = timeout(seconds) do
|
489
|
-
sleep WG_SLEEP_DELAY while (h = find_window nil, title) == nil; h
|
490
|
-
end
|
491
|
-
Window.new @handle
|
492
|
-
end
|
493
|
-
|
494
|
-
def initialize(handle)
|
495
|
-
@handle = handle
|
496
|
-
end
|
497
|
-
|
498
|
-
# find child window (control) by title, window class, or control ID:
|
499
|
-
def child(id)
|
500
|
-
result = case id
|
501
|
-
when String
|
502
|
-
by_title = find_window_ex @handle, 0, nil, id.gsub('_' , '&' )
|
503
|
-
by_class = find_window_ex @handle, 0, id, nil
|
504
|
-
by_title ? by_title : by_class
|
505
|
-
when Fixnum
|
506
|
-
get_dlg_item @handle, id
|
507
|
-
when nil
|
508
|
-
find_window_ex @handle, 0, nil, nil
|
509
|
-
else
|
510
|
-
nil
|
511
|
-
end
|
512
|
-
raise "Control '#{id}' not found" unless result
|
513
|
-
Window.new result
|
514
|
-
end
|
515
|
-
|
516
|
-
def children
|
517
|
-
enum_child_windows(@handle,'Msg').map{|child_handle| Window.new child_handle}
|
518
|
-
end
|
519
|
-
|
520
|
-
# emulate click of the control identified by id
|
521
|
-
def click(id)
|
522
|
-
h = child(id).handle
|
523
|
-
rectangle = [0, 0, 0, 0].pack 'LLLL'
|
524
|
-
get_window_rect h, rectangle
|
525
|
-
left, top, right, bottom = rectangle.unpack 'LLLL'
|
526
|
-
center = [(left + right) / 2, (top + bottom) / 2]
|
527
|
-
set_cursor_pos *center
|
528
|
-
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
|
529
|
-
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
|
530
|
-
end
|
531
|
-
|
532
|
-
def close
|
533
|
-
post_message @handle, WM_SYSCOMMAND, SC_CLOSE, 0
|
534
|
-
end
|
535
|
-
|
536
|
-
def wait_for_close
|
537
|
-
timeout(WG_CLOSE_TIMEOUT) do
|
538
|
-
sleep WG_SLEEP_DELAY while window_visible?(@handle)
|
539
|
-
end
|
540
|
-
end
|
541
|
-
|
542
|
-
def text
|
543
|
-
buffer = "\x0" * 2048
|
544
|
-
length = send_message @handle, WM_GETTEXT, buffer.length, buffer
|
545
|
-
length == 0 ? '' : buffer[0..length - 1]
|
546
|
-
end
|
547
|
-
end
|
548
|
-
|
549
607
|
end
|
550
608
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,20 +3,27 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
3
3
|
require 'spec'
|
4
4
|
require 'spec/autorun'
|
5
5
|
|
6
|
+
$debug = true
|
7
|
+
|
6
8
|
# Customize RSpec with my own extensions
|
7
9
|
module SpecMacros
|
8
10
|
|
9
11
|
# wrapper for it method that extracts description from example source code, such as:
|
10
12
|
# spec { use{ function(arg1 = 4, arg2 = 'string') }}
|
11
13
|
def spec &block
|
12
|
-
|
14
|
+
# p (block.methods-Object.methods).sort; exit
|
15
|
+
if RUBY_PLATFORM =~ /java/
|
16
|
+
it 'dummy JRuby description', &block
|
17
|
+
else
|
18
|
+
it description_from(*block.source_location), &block
|
19
|
+
end
|
13
20
|
end
|
14
21
|
|
15
22
|
# reads description line from source file and drops external brackets (like its{}, use{}
|
16
23
|
def description_from(file, line)
|
17
|
-
|
18
|
-
|
19
|
-
|
24
|
+
File.open(file) do |f|
|
25
|
+
f.lines.to_a[line-1].gsub( /(spec.*?{)|(use.*?{)|}/, '' ).strip
|
26
|
+
end
|
20
27
|
end
|
21
28
|
end
|
22
29
|
|
@@ -24,6 +31,7 @@ Spec::Runner.configure { |config| config.extend(SpecMacros) }
|
|
24
31
|
|
25
32
|
module WinTest
|
26
33
|
|
34
|
+
TEST_KEY_DELAY = 0.001
|
27
35
|
TEST_IMPOSSIBLE = 'Impossible'
|
28
36
|
TEST_CONVERSION_ERROR = /Can.t convert/
|
29
37
|
TEST_SLEEP_DELAY = 0.01
|
@@ -32,14 +40,15 @@ module WinTest
|
|
32
40
|
TEST_WIN_TITLE = 'LockNote - Steganos LockNote'
|
33
41
|
TEST_WIN_CLASS = 'ATL:00434098'
|
34
42
|
TEST_WIN_RECT = [710, 400, 1210, 800]
|
43
|
+
TEST_STATUSBAR_CLASS = 'msctls_statusbar32'
|
35
44
|
TEST_TEXTAREA_CLASS = 'ATL:00434310'
|
36
45
|
|
37
46
|
def use
|
38
|
-
lambda
|
47
|
+
lambda{yield}.should_not raise_error
|
39
48
|
end
|
40
49
|
|
41
50
|
def any_block
|
42
|
-
lambda
|
51
|
+
lambda{|*args| args}
|
43
52
|
end
|
44
53
|
|
45
54
|
def any_handle
|
@@ -53,5 +62,4 @@ module WinTest
|
|
53
62
|
def not_a_handle
|
54
63
|
123
|
55
64
|
end
|
56
|
-
|
57
65
|
end
|