win_gui 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|