win_gui 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +59 -1
- data/VERSION +1 -1
- data/lib/win_gui/constants.rb +63 -52
- data/lib/win_gui/def_api.rb +76 -25
- data/lib/win_gui/win_gui.rb +249 -105
- data/spec/spec_helper.rb +32 -25
- data/spec/win_gui/def_api_spec.rb +172 -121
- data/spec/win_gui/win_gui_spec.rb +9 -1
- data/win_gui.gemspec +2 -2
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -14,6 +14,64 @@ elements (such as WinGui::Window class and its methods).
|
|
14
14
|
|
15
15
|
It is still work in progress, only a small number of API functions wrapped so far...
|
16
16
|
|
17
|
+
== SUMMARY
|
18
|
+
|
19
|
+
So you want to write a simple program that makes some Win32 API function calls.
|
20
|
+
You searched MSDN and you know what functions you'll need. Now you just want to
|
21
|
+
put those calls into your Ruby code. You'd love it to be a natural extension of
|
22
|
+
Ruby code, preferably not turning your code base into a ugly C++ like spaghetty
|
23
|
+
of CamelCase calls, String/Array pack/unpack gymnastics, buffer allocations,
|
24
|
+
extracting return values from [in/out] parameters and checking return codes for 0.
|
25
|
+
You can definitely use excellent 'win32-api' gem by Daniel J. Berger and Park Heesob
|
26
|
+
that allows you to define Win32 API objects for any function you can find on MSDN,
|
27
|
+
execute calls on them and even define callback objects that some of those API functions expect.
|
28
|
+
|
29
|
+
However, this gem will only take you so far. You'll still have to handle (somewhat)
|
30
|
+
gory details of argument preparation, mimicking pointers with Strings and stuff.
|
31
|
+
For example, consider the amount of code needed to complete a task as simple as
|
32
|
+
getting unicode title text for the window that you already have handle for:
|
33
|
+
|
34
|
+
api = Win32::API.new( 'GetWindowTextW', ['L', 'P', 'I'], 'L', 'user32' )
|
35
|
+
buffer = "\x00" * 1024
|
36
|
+
num_chars = api.call( window_handle, buffer, buffer.size)
|
37
|
+
title = if num_chars == 0
|
38
|
+
nil
|
39
|
+
else
|
40
|
+
buffer.force_encoding('utf-16LE').encode('utf-8').rstrip
|
41
|
+
end
|
42
|
+
|
43
|
+
Ew, ugly. What about getting information about process id for a known window?
|
44
|
+
|
45
|
+
api = Win32::API.new( 'GetWindowThreadProcessId', ['L', 'P'], 'L' , 'user32' )
|
46
|
+
process_packed = [1].pack('L')
|
47
|
+
thread = api.call(window_handle, process_packed)
|
48
|
+
process = process.unpack('L').first
|
49
|
+
|
50
|
+
Wow, packing and unpacking arrays into String to get hold of a simple integer id.
|
51
|
+
Just great. Now, wouldn't it be MUCH better if you can just say something like this:
|
52
|
+
|
53
|
+
title = window_text( window_handle)
|
54
|
+
thread, process = window_thread_process_id( window_handle)
|
55
|
+
|
56
|
+
What about API functions expecting callbacks? Well, something like this may be nice:
|
57
|
+
|
58
|
+
enum_child_windows( parent_handle, message ){|child_handle, message| puts child_handle }
|
59
|
+
|
60
|
+
If you think about it, callbacks are not much than more than code blocks, so let's not
|
61
|
+
be afraid to treat them as such. It would be also good if test functions return true/false
|
62
|
+
instead of zero/nonzero, find functions return nil if nothing was found etc...
|
63
|
+
|
64
|
+
So this is an idea behind WinGui library - make Win32 API functions more fun to use
|
65
|
+
and feel more natural inside Ruby code. Following the principle of least surprise, we
|
66
|
+
define methods with Rubyesque names (minimized? instead of IsMinimized, etc), minimum
|
67
|
+
arguments with sensible defaults, explicit return values and generous use of attached blocks.
|
68
|
+
|
69
|
+
Well, we even keep a backup solution for those diehard Win32 API longtimers who would rather
|
70
|
+
allocate their buffer strings by hand and mess with obscure return codes. If you use original
|
71
|
+
CamelCase method name instead of Rubyesque snake_case one, it will expect those standard
|
72
|
+
parameters you know and love from MSDN, return your zeroes instead of nils and support no
|
73
|
+
other enhancements. Enjoy!
|
74
|
+
|
17
75
|
== DOCUMENTATION:
|
18
76
|
|
19
77
|
See WinGui and WinGui::Window for documentation
|
@@ -25,7 +83,7 @@ arguments given to block, etc...)
|
|
25
83
|
|
26
84
|
== FEATURES/PROBLEMS:
|
27
85
|
|
28
|
-
This project is quite new, so it's
|
86
|
+
This project is quite new, so it's may be not suitable for production-quality systems
|
29
87
|
Contributors always welcome!
|
30
88
|
|
31
89
|
== INSTALL:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
data/lib/win_gui/constants.rb
CHANGED
@@ -1,71 +1,82 @@
|
|
1
1
|
module WinGui
|
2
2
|
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
WG_SLEEP_DELAY = 0.001
|
3
|
+
# Delay between key commands (events)
|
4
|
+
WG_KEY_DELAY = 0.00001
|
5
|
+
# Wait delay quant
|
6
|
+
WG_SLEEP_DELAY = 0.001
|
7
|
+
# Timeout waiting for Window to be closed
|
7
8
|
WG_CLOSE_TIMEOUT = 1
|
8
|
-
|
9
|
-
|
9
|
+
|
10
10
|
# Windows keyboard-related Constants:
|
11
11
|
# Virtual key codes:
|
12
12
|
|
13
|
-
|
13
|
+
|
14
|
+
# Control-break processing
|
15
|
+
VK_CANCEL = 0x03
|
16
|
+
# Backspace? key
|
14
17
|
VK_BACK = 0x08
|
18
|
+
# Tab key
|
15
19
|
VK_TAB = 0x09
|
20
|
+
# Shift key
|
16
21
|
VK_SHIFT = 0x10
|
22
|
+
# Ctrl key
|
17
23
|
VK_CONTROL = 0x11
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
24
|
+
# ENTER key
|
25
|
+
VK_RETURN = 0x0D
|
26
|
+
# ALT key
|
27
|
+
VK_ALT = 0x12
|
28
|
+
# ALT key alias
|
29
|
+
VK_MENU = 0x12
|
30
|
+
# PAUSE key
|
31
|
+
VK_PAUSE = 0x13
|
32
|
+
# CAPS LOCK key
|
33
|
+
VK_CAPITAL = 0x14
|
34
|
+
# ESC key
|
35
|
+
VK_ESCAPE = 0x1B
|
36
|
+
# SPACEBAR
|
37
|
+
VK_SPACE = 0x20
|
38
|
+
# PAGE UP key
|
39
|
+
VK_PRIOR = 0x21
|
40
|
+
# PAGE DOWN key
|
41
|
+
VK_NEXT = 0x22
|
42
|
+
# END key
|
43
|
+
VK_END = 0x23
|
44
|
+
# HOME key
|
45
|
+
VK_HOME = 0x24
|
46
|
+
# LEFT ARROW key
|
47
|
+
VK_LEFT = 0x25
|
48
|
+
# UP ARROW key
|
49
|
+
VK_UP = 0x26
|
50
|
+
# RIGHT ARROW key
|
51
|
+
VK_RIGHT = 0x27
|
52
|
+
# DOWN ARROW key
|
53
|
+
VK_DOWN = 0x28
|
54
|
+
# SELECT key
|
55
|
+
VK_SELECT = 0x29
|
56
|
+
# PRINT key
|
57
|
+
VK_PRINT = 0x2A
|
58
|
+
# EXECUTE key
|
59
|
+
VK_EXECUTE = 0x2B
|
60
|
+
# PRINT SCREEN key
|
61
|
+
VK_SNAPSHOT = 0x2C
|
62
|
+
# INS key
|
63
|
+
VK_INSERT = 0x2D
|
64
|
+
# DEL key
|
65
|
+
VK_DELETE = 0x2E
|
66
|
+
# HELP key
|
67
|
+
VK_HELP = 0x2F
|
42
68
|
|
69
|
+
# Key down keyboard event
|
43
70
|
KEYEVENTF_KEYDOWN = 0
|
44
|
-
|
45
|
-
|
46
|
-
# Show Window Commands:
|
47
|
-
|
48
|
-
SW_HIDE = 0
|
49
|
-
SW_NORMAL = 1
|
50
|
-
SW_SHOWNORMAL = 1
|
51
|
-
SW_SHOWMINIMIZED = 2
|
52
|
-
SW_SHOWMAXIMIZED = 3
|
53
|
-
SW_MAXIMIZE = 3
|
54
|
-
SW_SHOWNOACTIVATE = 4
|
55
|
-
SW_SHOW = 5
|
56
|
-
SW_MINIMIZE = 6
|
57
|
-
SW_SHOWMINNOACTIVE= 7
|
58
|
-
SW_SHOWNA = 8
|
59
|
-
SW_RESTORE = 9
|
60
|
-
SW_SHOWDEFAULT = 10
|
61
|
-
SW_FORCEMINIMIZE = 11
|
71
|
+
# Key up keyboard event
|
72
|
+
KEYEVENTF_KEYUP = 2
|
62
73
|
|
63
|
-
# Windows
|
64
|
-
|
74
|
+
# Windows Message Get Text
|
65
75
|
WM_GETTEXT = 0x000D
|
76
|
+
# Windows Message Sys Command
|
66
77
|
WM_SYSCOMMAND = 0x0112
|
78
|
+
# Sys Command Close
|
67
79
|
SC_CLOSE = 0xF060
|
68
80
|
|
69
|
-
# Other Windows Constants:
|
70
81
|
end
|
71
82
|
|
data/lib/win_gui/def_api.rb
CHANGED
@@ -1,66 +1,97 @@
|
|
1
|
+
require 'Win32/api'
|
2
|
+
|
1
3
|
module WinGui
|
2
4
|
module DefApi
|
5
|
+
# DLL to use with API decarations by default ('user32')
|
3
6
|
DEFAULT_DLL = 'user32'
|
4
7
|
|
5
|
-
# Defines new
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
8
|
+
# Defines new method wrappers for Windows API function call:
|
9
|
+
# - Defines method with original (CamelCase) API function name and original signature (matches MSDN description)
|
10
|
+
# - Defines method with snake_case name (converted from CamelCase function name) with enhanced API signature
|
11
|
+
# When the defined wrapper method is called, it checks the argument count, executes underlying API
|
12
|
+
# function call and (optionally) transforms the result before returning it. If block is attached to
|
13
|
+
# method invocation, raw result is yielded to this block before final transformations
|
14
|
+
# - Defines aliases for enhanced method with more Rubyesque names for getters, setters and tests:
|
15
|
+
# GetWindowText -> window_test, SetWindowText -> window_text=, IsZoomed -> zoomed?
|
9
16
|
#
|
10
|
-
# You may modify default defined method
|
11
|
-
#
|
12
|
-
# and (optional) runtime block to &define_block
|
17
|
+
# You may modify default behavior of defined method by providing optional &define_block to def_api.
|
18
|
+
# If you do so, instead of directly calling API function, defined method just yields callable api
|
19
|
+
# object, arguments and (optional) runtime block to your &define_block and returns result coming out of it.
|
20
|
+
# So, &define_block should define all the behavior of defined method. You can use define_block to:
|
21
|
+
# - Change original signature of API function, provide argument defaults, check argument types
|
22
|
+
# - Pack arguments into strings for [in] or [in/out] parameters that expect a pointer
|
23
|
+
# - Allocate string buffers for pointers required by API functions [out] parameters
|
24
|
+
# - Unpack [out] and [in/out] parameters returned as pointers
|
25
|
+
# - Explicitly return results of API call that are returned in [out] and [in/out] parameters
|
26
|
+
# - Convert attached runtime blocks into callback functions and stuff them into [in] callback parameters
|
13
27
|
#
|
14
28
|
# Accepts following options:
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
29
|
+
# :dll:: Use this dll instead of default 'user32'
|
30
|
+
# :rename:: Use this name instead of standard (conventional) function name
|
31
|
+
# :alias(es):: Provides additional alias(es) for defined method
|
32
|
+
# :boolean:: Forces method to return true/false instead of nonzero/zero
|
33
|
+
# :zeronil:: Forces method to return nil if function result is zero
|
20
34
|
#
|
21
35
|
def def_api(function, params, returns, options={}, &define_block)
|
36
|
+
aliases = ([options[:alias]] + [options[:aliases]]).flatten.compact
|
22
37
|
name = options[:rename] || function.snake_case
|
23
|
-
|
24
|
-
|
25
|
-
|
38
|
+
case name
|
39
|
+
when /^is_/
|
40
|
+
aliases << name.sub(/^is_/, '') + '?'
|
41
|
+
boolean = true
|
42
|
+
when /^set_/
|
43
|
+
aliases << name.sub(/^set_/, '')+ '='
|
44
|
+
when /^get_/
|
45
|
+
aliases << name.sub(/^get_/, '')
|
26
46
|
end
|
27
47
|
boolean ||= options[:boolean]
|
28
48
|
zeronil = options[:zeronil]
|
29
|
-
aliases = ([options[:alias]] + [options[:aliases]]).flatten.compact
|
30
49
|
proto = params.respond_to?(:join) ? params.join : params # Converts params into prototype string
|
31
50
|
api = Win32::API.new(function, proto.upcase, returns.upcase, options[:dll] || DEFAULT_DLL)
|
32
51
|
|
33
|
-
define_method(
|
52
|
+
define_method(function) {|*args| api.call(*args)} # defines CamelCase method wrapper for api call
|
53
|
+
|
54
|
+
define_method(name) do |*args, &runtime_block| # defines snake_case method with enhanced api
|
34
55
|
return api if args == [:api]
|
35
56
|
return define_block.call(api, *args, &runtime_block) if define_block
|
36
|
-
|
57
|
+
unless args.size == params.size
|
58
|
+
raise ArgumentError, "wrong number of parameters: expected #{params.size}, got #{args.size}"
|
59
|
+
end
|
37
60
|
result = api.call(*args)
|
38
|
-
|
61
|
+
result = runtime_block[result] if runtime_block
|
39
62
|
return result != 0 if boolean # Boolean function returns true/false instead of nonzero/zero
|
40
63
|
return nil if zeronil && result == 0
|
41
64
|
result
|
42
65
|
end
|
43
|
-
aliases.each {|
|
66
|
+
aliases.each {|ali| alias_method ali, name } unless aliases == []
|
44
67
|
end
|
45
68
|
|
46
|
-
# Helper methods:
|
47
|
-
|
48
69
|
# Converts block into API::Callback object that can be used as API callback argument
|
70
|
+
#
|
49
71
|
def callback(params, returns, &block)
|
50
72
|
Win32::API::Callback.new(params, returns, &block)
|
51
73
|
end
|
52
74
|
|
75
|
+
private # Helper methods:
|
76
|
+
|
53
77
|
# Returns string buffer - used to supply string pointer reference to API functions
|
78
|
+
#
|
54
79
|
def buffer(size = 1024, code = "\x00")
|
55
80
|
code * size
|
56
81
|
end
|
57
82
|
|
58
83
|
# Procedure that returns (possibly encoded) string as a result of api function call
|
84
|
+
# or nil if zero characters was returned by api call
|
85
|
+
#
|
59
86
|
def return_string( encode = nil )
|
60
87
|
lambda do |api, *args|
|
61
|
-
|
88
|
+
num_params = api.prototype.size-2
|
89
|
+
unless args.size == num_params
|
90
|
+
raise ArgumentError, "wrong number of parameters: expected #{num_params}, got #{args.size}"
|
91
|
+
end
|
62
92
|
args += [string = buffer, string.length]
|
63
|
-
num_chars = api.call(*args)
|
93
|
+
num_chars = api.call(*args)
|
94
|
+
return nil if num_chars == 0
|
64
95
|
string = string.force_encoding('utf-16LE').encode(encode) if encode
|
65
96
|
string.rstrip
|
66
97
|
end
|
@@ -69,9 +100,13 @@ module WinGui
|
|
69
100
|
# Procedure that calls api function expecting a callback. If runtime block is given
|
70
101
|
# it is converted into callback, otherwise procedure returns an array of all handles
|
71
102
|
# pushed into callback by api enumeration
|
103
|
+
#
|
72
104
|
def return_enum
|
73
105
|
lambda do |api, *args, &block|
|
74
|
-
|
106
|
+
num_params = api.prototype.size-1
|
107
|
+
unless args.size == num_params
|
108
|
+
raise ArgumentError, "wrong number of parameters: expected #{num_params}, got #{args.size}"
|
109
|
+
end
|
75
110
|
handles = []
|
76
111
|
cb = if block
|
77
112
|
callback('LP', 'I', &block)
|
@@ -87,5 +122,21 @@ module WinGui
|
|
87
122
|
end
|
88
123
|
end
|
89
124
|
|
125
|
+
# Procedure that calls (DdeInitialize) function expecting a DdeCallback. Runtime block is converted
|
126
|
+
# into Dde callback and registered with DdeInitialize. Returns DDE init status and DDE instance id.
|
127
|
+
#
|
128
|
+
# TODO: Pushed into this module since RubyMine (wrongly) reports error on lambda args
|
129
|
+
#
|
130
|
+
def return_id_status
|
131
|
+
lambda do |api, id=0, cmd, &block|
|
132
|
+
raise ArgumentError, 'No callback block' unless block
|
133
|
+
callback = callback 'IIPPPPPP', 'L', &block
|
134
|
+
id = [id].pack('L')
|
135
|
+
|
136
|
+
status = api.call(id, callback, cmd, 0)
|
137
|
+
[*id.unpack('L'), status]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
90
141
|
end
|
91
142
|
end
|