win 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +149 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/features/step_definitions/win_steps.rb +0 -0
- data/features/support/env.rb +4 -0
- data/features/win.feature +9 -0
- data/lib/win/extensions.rb +24 -0
- data/lib/win/library.rb +198 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/win/extensions_spec.rb +63 -0
- data/spec/win/library_spec.rb +370 -0
- metadata +91 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 arvicco
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
= win
|
2
|
+
|
3
|
+
by: Arvicco
|
4
|
+
url: http://github.com/arvicco/win_gui
|
5
|
+
|
6
|
+
== DESCRIPTION
|
7
|
+
|
8
|
+
A collection of Windows functions predefined for you using FFI. In addition to
|
9
|
+
straightforward (CamelCase) API wrappers, it also strives to provide more Ruby-like
|
10
|
+
snake_case methods that take a minimum of arguments with sensible defaults and have
|
11
|
+
sensible return values (false/true instead of 0/nonzero for test functions, etc).
|
12
|
+
|
13
|
+
Windows functions are grouped by topics and defined in separate namespaces (modules),
|
14
|
+
that also contain related constants and convenience methods. For example, win/dde.rb
|
15
|
+
file contains functions related to DDE protocol, such as DdeInitialize() as well as
|
16
|
+
constants such as DMLERR_NO_ERROR, APPCLASS_STANDARD, etc.
|
17
|
+
|
18
|
+
It is still work in progress, only a small number of Windows API functions wrapped so far...
|
19
|
+
|
20
|
+
== SUMMARY
|
21
|
+
|
22
|
+
So you want to write a simple program that makes some Windows API function calls.
|
23
|
+
You searched MSDN high and low and you now know exactly what functions you need.
|
24
|
+
All you want is just putting those function calls into your Ruby code without too
|
25
|
+
much pain. You'd love this to be more or less natural extension of your Ruby code,
|
26
|
+
preferably not turning your code base into an ugly spaghetty of CamelCase calls,
|
27
|
+
String/Array pack/unpack gymnastics, buffer/pointer allocations, extracting return
|
28
|
+
values from [in/out] parameters and checking return codes for 0.
|
29
|
+
|
30
|
+
You have several options at this point. You can use either 'win32-api' or 'ffi' libraries
|
31
|
+
to connect your ruby code to Windows API and manually define wrapper methods for
|
32
|
+
needed function calls. This is definitely a valid approach, even if it is a bit
|
33
|
+
low-level one: you'll still have to handle (somewhat) gory details of callback
|
34
|
+
announcement, argument preparation, mimicking pointers with Strings (or declaring
|
35
|
+
pointers explicitly with FFI) and other stuff (like manually defining about a gazillion
|
36
|
+
obscure Windows constants). As an example, consider the amount of code needed to complete
|
37
|
+
a task as simple as getting unicode title text for the window that you already have handle
|
38
|
+
for (using win32-api):
|
39
|
+
|
40
|
+
api = Win32::API.new( 'GetWindowTextW', ['L', 'P', 'I'], 'L', 'user32' )
|
41
|
+
buffer = "\x00" * 1024 # I just hope it will be enough...
|
42
|
+
num_chars = api.call(window_handle, buffer, buffer.size)
|
43
|
+
title = if num_chars == 0
|
44
|
+
nil
|
45
|
+
else
|
46
|
+
buffer.force_encoding('utf-16LE').encode('utf-8').rstrip
|
47
|
+
end
|
48
|
+
|
49
|
+
This is how you achieve the same result with ffi:
|
50
|
+
|
51
|
+
extend FFI::Library
|
52
|
+
ffi_lib 'user32'
|
53
|
+
ffi_convention :stdcall
|
54
|
+
attach_function :GetWindowTextW, [ :long, :buffer_out, :int ], :long
|
55
|
+
buffer = FFI::Buffer.new 1024
|
56
|
+
buffer.put_string(0, "\x00" * 1024)
|
57
|
+
num_chars = GetWindowTextW(window_handle, buffer, 1024)
|
58
|
+
title = if num_chars == 0
|
59
|
+
nil
|
60
|
+
else
|
61
|
+
buffer.get_bytes(0, num_chars).force_encoding('utf-16LE').encode('utf-8').rstrip
|
62
|
+
end
|
63
|
+
|
64
|
+
As an alternative, you can use 'windows-pr' (pure ruby) library that gives you lots of
|
65
|
+
Windows functions pre-defined and sectioned into modules, declares Windows constants and
|
66
|
+
adds some other niceties. Unfortunately this library works only with MRI (not JRuby or
|
67
|
+
other Ruby implementations), and still lacks Ruby look-and-feel for declared functions.
|
68
|
+
It helps you to cut some of the declaration slack:
|
69
|
+
|
70
|
+
num_chars = GetWindowTextW(window_handle, buffer ="\x00" * 1024 , buffer.size)
|
71
|
+
title = if num_chars == 0
|
72
|
+
nil
|
73
|
+
else
|
74
|
+
buffer.force_encoding('utf-16LE').encode('utf-8').rstrip
|
75
|
+
end
|
76
|
+
|
77
|
+
But still, does not it look like TOO MUCH code for something that should (ideally) look like this:
|
78
|
+
|
79
|
+
title = window_text_w(window_handle)
|
80
|
+
|
81
|
+
This is an idea behind this library - make Windows API functions easier to use and feel more
|
82
|
+
natural inside Ruby code. Following the principle of least surprise, we define methods that:
|
83
|
+
* Have Rubyesque names (minimized? instead of IsMinimized, etc)
|
84
|
+
* Require minimum arguments with sensible defaults
|
85
|
+
* Return appropriate values explicitly (several return values if necessary)
|
86
|
+
* Have sensible returns (false/true instead of 0/nonzero for test functions, nil if find function fails, etc)
|
87
|
+
* Accept blocks where callback is expected
|
88
|
+
* Are partitioned into related namespaces, so that you can load only the modules you need
|
89
|
+
|
90
|
+
Well, we even keep a backup solution for those diehard Win32 API longtimers who would rather
|
91
|
+
allocate their buffer strings by hand and mess with obscure return codes. If you use original
|
92
|
+
CamelCase method name instead of Rubyesque snake_case one, it will expect those standard
|
93
|
+
parameters you know and love from MSDN, return your zeroes instead of nils and support no
|
94
|
+
other enhancements.
|
95
|
+
|
96
|
+
And if you do not see your favorite Windows API functions amoung those already defined, it is
|
97
|
+
quite easy to define new ones with 'function' macro method from Win::Library class - it does a
|
98
|
+
lot of heavy lifting for you and can be customized with options and code blocks to give you
|
99
|
+
reusable API wrapper methods with the exact behavior you need.
|
100
|
+
|
101
|
+
== REQUIREMENTS:
|
102
|
+
|
103
|
+
Only works with Ruby 1.9 compatible implementations since it uses some of the most recent features
|
104
|
+
(block arguments given to block, etc...)
|
105
|
+
|
106
|
+
== FEATURES/PROBLEMS:
|
107
|
+
|
108
|
+
This project is quite new, so it may be not suitable for production-quality systems yet.
|
109
|
+
Contributors always welcome!
|
110
|
+
|
111
|
+
== INSTALLATION
|
112
|
+
|
113
|
+
$ gem install win
|
114
|
+
|
115
|
+
== SYNOPSIS
|
116
|
+
|
117
|
+
require 'win/window'
|
118
|
+
|
119
|
+
class Foo
|
120
|
+
include Win::Window
|
121
|
+
|
122
|
+
fg_window = foreground_window
|
123
|
+
puts window_text_w(fg_window)
|
124
|
+
show_window(fg_window) unless minimized?(fg_window)
|
125
|
+
hide_window(fg_window) if maximized?(fg_window)
|
126
|
+
...
|
127
|
+
end
|
128
|
+
|
129
|
+
== CREDITS:
|
130
|
+
|
131
|
+
This library started as an extension of ideas and code described in excellent book
|
132
|
+
"Scripted GUI Testing with Ruby" by Ian Dees. 'win32-api' and 'windows-pr' gems by
|
133
|
+
Daniel J. Berger and Park Heesob provided both inspiration and an excellent source
|
134
|
+
for code borrowing. 'ffi' gem serves as a solid basis for this library, allowing to
|
135
|
+
use it for multiple Ruby implementations.
|
136
|
+
|
137
|
+
== Note on Patches/Pull Requests
|
138
|
+
|
139
|
+
* Fork the project.
|
140
|
+
* Make your feature addition or bug fix.
|
141
|
+
* Add tests for it. This is important so I don't break it in a
|
142
|
+
future version unintentionally.
|
143
|
+
* Commit, do not mess with rakefile, version, or history.
|
144
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
145
|
+
* Send me a pull request. Bonus points for topic branches.
|
146
|
+
|
147
|
+
== Copyright
|
148
|
+
|
149
|
+
Copyright (c) 2010 arvicco. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "win"
|
8
|
+
gem.summary = %Q{A collection of Windows functions predefined for you using FFI}
|
9
|
+
gem.description = %Q{A collection of Windows functions predefined for you using FFI}
|
10
|
+
gem.email = "arvitallian@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/arvicco/win"
|
12
|
+
gem.authors = ["arvicco"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_development_dependency "cucumber", ">= 0"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
begin
|
37
|
+
require 'cucumber/rake/task'
|
38
|
+
Cucumber::Rake::Task.new(:features)
|
39
|
+
|
40
|
+
task :features => :check_dependencies
|
41
|
+
rescue LoadError
|
42
|
+
task :features do
|
43
|
+
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
task :default => :spec
|
48
|
+
|
49
|
+
require 'rake/rdoctask'
|
50
|
+
Rake::RDocTask.new do |rdoc|
|
51
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
52
|
+
|
53
|
+
rdoc.rdoc_dir = 'rdoc'
|
54
|
+
rdoc.title = "win #{version}"
|
55
|
+
rdoc.rdoc_files.include('README*')
|
56
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
57
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.2
|
File without changes
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class String
|
2
|
+
def snake_case
|
3
|
+
gsub(/([a-z])([A-Z0-9])/, '\1_\2' ).downcase
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_w
|
7
|
+
(self+"\x00").encode('utf-16LE')
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_vkeys
|
11
|
+
unless size == 1
|
12
|
+
raise "Can't convert but a single character: #{self}"
|
13
|
+
end
|
14
|
+
ascii = upcase.unpack('C')[0]
|
15
|
+
case self
|
16
|
+
when 'a'..'z', '0'..'9', ' '
|
17
|
+
[ascii]
|
18
|
+
when 'A'..'Z'
|
19
|
+
[0x10, ascii] # Win.const_get(:VK_SHIFT) = 0x10 Bad coupling
|
20
|
+
else
|
21
|
+
raise "Can't convert unknown character: #{self}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/win/library.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'win/extensions'
|
3
|
+
|
4
|
+
module Win
|
5
|
+
|
6
|
+
class NotFoundError < NameError
|
7
|
+
def initialize(name=nil, libs=nil)
|
8
|
+
super %Q[Function #{name ? "'#{name}' ": ""}not found#{libs ? " in #{libs}" : ""}"]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Library
|
13
|
+
|
14
|
+
# Class Win::Library::API mimics Win32::API
|
15
|
+
class API
|
16
|
+
|
17
|
+
# The name of the DLL that exports the API function
|
18
|
+
attr_reader :dll_name
|
19
|
+
|
20
|
+
# Ruby (library) module to which this function is attached
|
21
|
+
attr_reader :lib_module
|
22
|
+
|
23
|
+
# The name of the function passed to the constructor
|
24
|
+
attr_reader :function_name
|
25
|
+
|
26
|
+
# The name of the actual function that is returned by the constructor.
|
27
|
+
# For example, if you passed 'GetUserName' to the constructor, then the
|
28
|
+
# effective function name would be either 'GetUserNameA' or 'GetUserNameW'.
|
29
|
+
attr_accessor :effective_function_name
|
30
|
+
|
31
|
+
# The prototype, returned as an array of FFI types
|
32
|
+
attr_reader :prototype
|
33
|
+
|
34
|
+
# The return type (:void for no return value)
|
35
|
+
attr_reader :return_type
|
36
|
+
|
37
|
+
def initialize( lib_module, function, effective_function_name, prototype, return_type )
|
38
|
+
@lib_module = lib_module
|
39
|
+
@function_name = function
|
40
|
+
@effective_function_name = effective_function_name
|
41
|
+
@prototype = prototype
|
42
|
+
@return_type = return_type
|
43
|
+
end
|
44
|
+
|
45
|
+
def call( *args )
|
46
|
+
@lib_module.send(@effective_function_name.to_sym, *args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Contains class methods (macros) to be used in Win::Libraries
|
51
|
+
module ClassMethods
|
52
|
+
TYPES = {
|
53
|
+
V: :void, # For functions that return nothing (return type void).
|
54
|
+
v: :void, # For functions that return nothing (return type void).
|
55
|
+
C: :uchar, #– 8-bit unsigned character (byte)
|
56
|
+
c: :char, # 8-bit character (byte)
|
57
|
+
# :int8 – 8-bit signed integer
|
58
|
+
# :uint8 – 8-bit unsigned integer
|
59
|
+
S: :ushort, # – 16-bit unsigned integer (Win32API: used for string)
|
60
|
+
s: :short, # – 16-bit signed integer
|
61
|
+
# :uint16 – 16-bit unsigned integer
|
62
|
+
# :int16 – 16-bit signed integer
|
63
|
+
I: :uint, # 32-bit unsigned integer
|
64
|
+
i: :int, # 32-bit signed integer
|
65
|
+
# :uint32 – 32-bit unsigned integer
|
66
|
+
# :int32 – 32-bit signed integer
|
67
|
+
L: :ulong, # unsigned long int – platform-specific size
|
68
|
+
l: :long, # long int – platform-specific size (http://groups.google.com/group/ruby-ffi/browse_thread/thread/4762fc77130339b1)
|
69
|
+
# :int64 – 64-bit signed integer
|
70
|
+
# :uint64 – 64-bit unsigned integer
|
71
|
+
# :long_long – 64-bit signed integer
|
72
|
+
# :ulong_long – 64-bit unsigned integer
|
73
|
+
F: :float, # 32-bit floating point
|
74
|
+
D: :double, # 64-bit floating point (double-precision)
|
75
|
+
P: :pointer, # pointer – platform-specific size
|
76
|
+
p: :string, # C-style (NULL-terminated) character string (Win32API: S)
|
77
|
+
B: :bool # (?? 1 byte in C++)
|
78
|
+
#For function argument type only:
|
79
|
+
# :buffer_in – Similar to :pointer, but optimized for Buffers that the function can only read (not write).
|
80
|
+
# :buffer_out – Similar to :pointer, but optimized for Buffers that the function can only write (not read).
|
81
|
+
# :buffer_inout – Similar to :pointer, but may be optimized for Buffers.
|
82
|
+
# :varargs – Variable arguments
|
83
|
+
}
|
84
|
+
|
85
|
+
##
|
86
|
+
# Defines new method wrappers for Windows API function call:
|
87
|
+
# - Defines method with original (CamelCase) API function name and original signature (matches MSDN description)
|
88
|
+
# - Defines method with snake_case name (converted from CamelCase function name) with enhanced API signature
|
89
|
+
# When the defined wrapper method is called, it checks the argument count, executes underlying API
|
90
|
+
# function call and (optionally) transforms the result before returning it. If block is attached to
|
91
|
+
# method invocation, raw result is yielded to this block before final transformations
|
92
|
+
# - Defines aliases for enhanced method with more Rubyesque names for getters, setters and tests:
|
93
|
+
# GetWindowText -> window_test, SetWindowText -> window_text=, IsZoomed -> zoomed?
|
94
|
+
#
|
95
|
+
# You may modify default behavior of defined method by providing optional &define_block to def_api.
|
96
|
+
# If you do so, instead of directly calling API function, defined method just yields callable api
|
97
|
+
# object, arguments and (optional) runtime block to your &define_block and returns result coming out of it.
|
98
|
+
# So, &define_block should define all the behavior of defined method. You can use define_block to:
|
99
|
+
# - Change original signature of API function, provide argument defaults, check argument types
|
100
|
+
# - Pack arguments into strings for [in] or [in/out] parameters that expect a pointer
|
101
|
+
# - Allocate string buffers for pointers required by API functions [out] parameters
|
102
|
+
# - Unpack [out] and [in/out] parameters returned as pointers
|
103
|
+
# - Explicitly return results of API call that are returned in [out] and [in/out] parameters
|
104
|
+
# - Convert attached runtime blocks into callback functions and stuff them into [in] callback parameters
|
105
|
+
#
|
106
|
+
# Accepts following options:
|
107
|
+
# :dll:: Use this dll instead of default 'user32'
|
108
|
+
# :rename:: Use this name instead of standard (conventional) function name
|
109
|
+
# :alias(es):: Provides additional alias(es) for defined method
|
110
|
+
# :boolean:: Forces method to return true/false instead of nonzero/zero
|
111
|
+
# :zeronil:: Forces method to return nil if function result is zero
|
112
|
+
#
|
113
|
+
def function(function, params, returns, options={}, &define_block)
|
114
|
+
method_name, effective_names, aliases = generate_names(function, options)
|
115
|
+
boolean = options[:boolean]
|
116
|
+
zeronil = options[:zeronil]
|
117
|
+
params = params.split(//) if params.respond_to?(:split) # Convert params string into array
|
118
|
+
#puts "#{method_name}, #{effective_names}, #{aliases}"
|
119
|
+
params.map! {|param| TYPES[param.to_sym] || param} # Convert chars into FFI type symbols
|
120
|
+
returns = TYPES[returns.to_sym] || returns # Convert chars into FFI type symbols
|
121
|
+
|
122
|
+
effective = effective_names.inject(nil) do |func, ename|
|
123
|
+
func || begin
|
124
|
+
#p ename, params, returns
|
125
|
+
attach_function(ename, params.dup, returns)
|
126
|
+
ename
|
127
|
+
rescue FFI::NotFoundError
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
raise Win::NotFoundError.new(function, ffi_libraries.map(&:name)) unless effective
|
133
|
+
|
134
|
+
api = API.new(self, function, effective, params, returns)
|
135
|
+
|
136
|
+
define_method(function) {|*args| api.call(*args)} # define CamelCase method wrapper for api call
|
137
|
+
|
138
|
+
if define_block
|
139
|
+
define_method(method_name) do |*args, &runtime_block|
|
140
|
+
define_block[api, *args, &runtime_block]
|
141
|
+
end
|
142
|
+
else
|
143
|
+
define_method(method_name) do |*args, &runtime_block| # define snake_case method with enhanced api
|
144
|
+
namespace.enforce_count(args, params)
|
145
|
+
result = api.call(*args)
|
146
|
+
result = runtime_block[result] if runtime_block
|
147
|
+
return result != 0 if boolean # Boolean function returns true/false instead of nonzero/zero
|
148
|
+
return nil if zeronil && result == 0 # Zeronil function returns nil instead of zero
|
149
|
+
result
|
150
|
+
end
|
151
|
+
end
|
152
|
+
aliases.each {|ali| alias_method ali, method_name } # define aliases
|
153
|
+
api # return API object
|
154
|
+
end
|
155
|
+
|
156
|
+
# Generates possible effective names for function in Win32 dll (function+A/W),
|
157
|
+
# Rubyesque name and aliases for method(s) defined based on function name,
|
158
|
+
# sets boolean flag for test functions (Is...)
|
159
|
+
#
|
160
|
+
def generate_names(function, options={})
|
161
|
+
effective_names = [function]
|
162
|
+
effective_names += ["#{function}A", "#{function}W"] unless function =~ /[WA]$/
|
163
|
+
aliases = ([options[:alias]] + [options[:aliases]]).flatten.compact
|
164
|
+
method_name = options[:rename] || function.snake_case
|
165
|
+
case method_name
|
166
|
+
when /^is_/
|
167
|
+
aliases << method_name.sub(/^is_/, '') + '?'
|
168
|
+
options[:boolean] = true
|
169
|
+
when /^set_/
|
170
|
+
aliases << method_name.sub(/^set_/, '')+ '='
|
171
|
+
when /^get_/
|
172
|
+
aliases << method_name.sub(/^get_/, '')
|
173
|
+
end
|
174
|
+
[method_name, effective_names, aliases]
|
175
|
+
end
|
176
|
+
|
177
|
+
# Ensures that args count is equal to params count plus diff
|
178
|
+
#
|
179
|
+
def enforce_count(args, params, diff = 0)
|
180
|
+
num_args = args.size
|
181
|
+
num_params = params.size + diff #params == 'V' ? 0 : params.size + diff
|
182
|
+
if num_args != num_params
|
183
|
+
raise ArgumentError, "wrong number of arguments (#{num_args} for #{num_params})"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
#extend ClassMethods
|
189
|
+
|
190
|
+
def self.included(klass)
|
191
|
+
klass.extend ClassMethods
|
192
|
+
klass.extend FFI::Library
|
193
|
+
klass.ffi_lib 'user32', 'kernel32' # Default libraries
|
194
|
+
klass.ffi_convention :stdcall
|
195
|
+
klass.class_eval "def namespace; #{klass}; end"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'spec'
|
4
|
+
require 'spec/autorun'
|
5
|
+
|
6
|
+
# Customize RSpec with my own extensions
|
7
|
+
module SpecMacros
|
8
|
+
|
9
|
+
# wrapper for it method that extracts description from example source code, such as:
|
10
|
+
# spec { use{ function(arg1 = 4, arg2 = 'string') }}
|
11
|
+
def spec &block
|
12
|
+
it description_from(*block.source_location), &block
|
13
|
+
end
|
14
|
+
|
15
|
+
# reads description line from source file and drops external brackets (like its{}, use{}
|
16
|
+
def description_from(file, line)
|
17
|
+
File.open(file) do |f|
|
18
|
+
f.lines.to_a[line-1].gsub( /(spec.*?{)|(use.*?{)|}/, '' ).strip
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Spec::Runner.configure { |config| config.extend(SpecMacros) }
|
24
|
+
|
25
|
+
module WinTest
|
26
|
+
|
27
|
+
TEST_IMPOSSIBLE = 'Impossible'
|
28
|
+
TEST_CONVERSION_ERROR = /Can.t convert/
|
29
|
+
|
30
|
+
def use
|
31
|
+
lambda {yield}.should_not raise_error
|
32
|
+
end
|
33
|
+
|
34
|
+
def any_block
|
35
|
+
lambda {|*args| args}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), ".." , "spec_helper" )
|
2
|
+
require 'win/extensions'
|
3
|
+
|
4
|
+
module WinTest
|
5
|
+
|
6
|
+
describe String do
|
7
|
+
context '#snake_case' do
|
8
|
+
it 'transforms CamelCase strings' do
|
9
|
+
'GetCharWidth32'.snake_case.should == 'get_char_width_32'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'leaves snake_case strings intact' do
|
13
|
+
'keybd_event'.snake_case.should == 'keybd_event'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context '#to_w' do
|
18
|
+
it 'transcodes string to utf-16LE' do
|
19
|
+
'GetCharWidth32'.to_w.encoding.name.should == 'UTF-16LE'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'ensures that encoded string is null-terminated' do
|
23
|
+
'GetCharWidth32'.to_w.bytes.to_a[-2..-1].should == [0, 0]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context '#to_vkeys' do
|
28
|
+
it 'transforms number char into [equivalent key code]' do
|
29
|
+
('0'..'9').each {|char| char.to_vkeys.should == char.unpack('C')}
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'transforms uppercase letters into [shift, equivalent key code]' do
|
33
|
+
('A'..'Z').each {|char| char.to_vkeys.should == [0x10, *char.unpack('C')]}
|
34
|
+
# Win.const_get(:VK_SHIFT) = 0x10 Bad coupling
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'transforms lowercase letters into [(upcase) key code]' do
|
38
|
+
('a'..'z').each {|char| char.to_vkeys.should == char.upcase.unpack('C')}
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'transforms space into [equivalent key code]' do
|
42
|
+
" ".to_vkeys.should == " ".unpack('C')
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'raises error if char is not implemented punctuation' do
|
46
|
+
('!'..'/').each {|char| lambda {char.to_vkeys}.should raise_error TEST_CONVERSION_ERROR }
|
47
|
+
(':'..'@').each {|char| lambda {char.to_vkeys}.should raise_error TEST_CONVERSION_ERROR }
|
48
|
+
('['..'`').each {|char| lambda {char.to_vkeys}.should raise_error TEST_CONVERSION_ERROR }
|
49
|
+
('{'..'~').each {|char| lambda {char.to_vkeys}.should raise_error TEST_CONVERSION_ERROR }
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'raises error if char is non-printable or non-ascii' do
|
53
|
+
lambda {1.chr.to_vkeys}.should raise_error TEST_CONVERSION_ERROR
|
54
|
+
lambda {230.chr.to_vkeys}.should raise_error TEST_CONVERSION_ERROR
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'raises error if string is multi-char' do
|
58
|
+
lambda {'hello'.to_vkeys}.should raise_error TEST_CONVERSION_ERROR
|
59
|
+
lambda {'23'.to_vkeys}.should raise_error TEST_CONVERSION_ERROR
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,370 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require 'win/library'
|
3
|
+
|
4
|
+
module WinTest
|
5
|
+
|
6
|
+
module MyLib # namespace for defined functions
|
7
|
+
include Win::Library
|
8
|
+
end
|
9
|
+
include MyLib
|
10
|
+
|
11
|
+
def should_be symbol, api
|
12
|
+
case symbol
|
13
|
+
when :find_window
|
14
|
+
#api.dll_name.should == 'user32' # The name of the DLL that exports the API function
|
15
|
+
api.effective_function_name.should == 'FindWindowA' # Actual function returned by the constructor: 'GetUserName' ->'GetUserNameA' or 'GetUserNameW'
|
16
|
+
api.function_name.should == 'FindWindow' # The name of the function passed to the constructor
|
17
|
+
api.prototype.should == [:pointer, :pointer] # The prototype, returned as an array of characters
|
18
|
+
api.return_type.should == :ulong # The prototype, returned as an array of characters
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def should_count_args(*methods, rights, wrongs)
|
23
|
+
rights = [rights].flatten
|
24
|
+
wrongs = [wrongs].flatten
|
25
|
+
methods.each do |method|
|
26
|
+
(0..8).each do |n|
|
27
|
+
if n == rights.size
|
28
|
+
expect {send method, *rights}.to_not raise_error
|
29
|
+
else
|
30
|
+
args = (1..n).map {wrongs[rand(wrongs.size)]}
|
31
|
+
expect {send method, *args}.
|
32
|
+
to raise_error "wrong number of arguments (#{args.size} for #{rights.size})"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def any_handle
|
39
|
+
MyLib.function 'FindWindow', 'PP', 'L' unless respond_to? :find_window
|
40
|
+
find_window(nil, nil)
|
41
|
+
end
|
42
|
+
|
43
|
+
def not_a_handle
|
44
|
+
123
|
45
|
+
end
|
46
|
+
|
47
|
+
def redefined_methods
|
48
|
+
[:FindWindow, :IsWindow, :EnumWindows, :GetComputerName, :GetForegroundWindow]
|
49
|
+
end
|
50
|
+
|
51
|
+
def hide_method(*names) # hide original method(s) if it is defined
|
52
|
+
names.map(&:to_s).each do |name|
|
53
|
+
MyLib.module_eval do
|
54
|
+
aliases = generate_names(name).flatten + [name]
|
55
|
+
aliases.map(&:to_s).each do |ali|
|
56
|
+
if method_defined? ali
|
57
|
+
alias_method "original_#{ali}".to_sym, ali
|
58
|
+
remove_method ali
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def restore_method(*names) # restore original method if it was hidden
|
66
|
+
names.map(&:to_s).each do |name|
|
67
|
+
MyLib.module_eval do
|
68
|
+
aliases = generate_names(name).flatten + [name]
|
69
|
+
aliases.map(&:to_s).each do |ali|
|
70
|
+
temp = "original_#{ali}".to_sym
|
71
|
+
if method_defined? temp
|
72
|
+
alias_method ali, temp
|
73
|
+
remove_method temp
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe Win::Library, ' defines wrappers for Win32::API functions' do
|
81
|
+
|
82
|
+
before(:each) { hide_method *redefined_methods } # hide original methods if defined
|
83
|
+
after(:each) { restore_method *redefined_methods } # restore original methods if hidden
|
84
|
+
|
85
|
+
context 'defining enhanced API function method' do
|
86
|
+
spec{ use{ MyLib.function('FindWindow', 'PP', 'l', rename: nil, aliases: nil, boolean: nil, zeronil: nil, &any_block) }}
|
87
|
+
|
88
|
+
it 'defines new instance methods with appropriate names' do
|
89
|
+
MyLib.function 'FindWindow', 'PP', 'L'
|
90
|
+
respond_to?(:FindWindow).should be_true
|
91
|
+
respond_to?(:find_window).should be_true
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'constructs argument prototype from uppercase string, enforces the args count' do
|
95
|
+
expect { MyLib.function 'FindWindow', 'PP', 'L' }.to_not raise_error
|
96
|
+
should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'constructs argument prototype from (mixed) array, enforces the args count' do
|
100
|
+
expect { MyLib.function 'FindWindow', [:pointer, 'P'], 'L' }.to_not raise_error
|
101
|
+
should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'with :rename option, overrides snake_case name for defined method but leaves CamelCase intact' do
|
105
|
+
MyLib.function 'FindWindow', 'PP', 'L', :rename=> 'my_own_find'
|
106
|
+
expect {find_window(nil, nil)}.to raise_error NoMethodError
|
107
|
+
expect {FindWindow(nil, nil)}.to_not raise_error
|
108
|
+
expect {my_own_find(nil, nil)}.to_not raise_error
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'defined snake_case method returns expected value when called' do
|
112
|
+
MyLib.function 'FindWindow', 'PP', 'L'
|
113
|
+
find_window(nil, nil).should_not == 0
|
114
|
+
find_window(nil, TEST_IMPOSSIBLE).should == 0
|
115
|
+
find_window(TEST_IMPOSSIBLE, nil).should == 0
|
116
|
+
find_window(TEST_IMPOSSIBLE, TEST_IMPOSSIBLE).should == 0
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'defined CamelCase method returns expected value when called' do
|
120
|
+
MyLib.function 'FindWindow', 'PP', 'L'
|
121
|
+
FindWindow(nil, nil).should_not == 0
|
122
|
+
FindWindow(nil, TEST_IMPOSSIBLE).should == 0
|
123
|
+
FindWindow(TEST_IMPOSSIBLE, nil).should == 0
|
124
|
+
FindWindow(TEST_IMPOSSIBLE, TEST_IMPOSSIBLE).should == 0
|
125
|
+
end
|
126
|
+
|
127
|
+
# it 'returns underlying Win32::API object if defined method is called with (:api) argument ' do
|
128
|
+
# MyLib.function 'FindWindow', 'PP', 'L'
|
129
|
+
# expect {find_window(:api)}.to_not raise_error
|
130
|
+
# should_be :find_window, find_window(:api)
|
131
|
+
# end
|
132
|
+
|
133
|
+
it 'returns underlying Win32::API object' do
|
134
|
+
FWAPI = MyLib.function 'FindWindow', 'PP', 'L'
|
135
|
+
should_be :find_window, FWAPI
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'defining aliases' do
|
140
|
+
it 'adds alias for defined method with :alias option' do
|
141
|
+
MyLib.function( 'FindWindow', 'PP', 'L', :alias => 'my_own_find')
|
142
|
+
expect {find_window(nil, nil)}.to_not raise_error
|
143
|
+
expect {my_own_find(nil, nil)}.to_not raise_error
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'adds aliases for defined method with :aliases option' do
|
147
|
+
MyLib.function 'FindWindow', 'PP', 'L', :aliases => ['my_own_find', 'my_own_find1']
|
148
|
+
expect {find_window(nil, nil)}.to_not raise_error
|
149
|
+
expect {my_own_find(nil, nil)}.to_not raise_error
|
150
|
+
expect {my_own_find1(nil, nil)}.to_not raise_error
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'adds Rubyesque alias to IsXxx API test function' do
|
154
|
+
MyLib.function 'IsWindow', 'L', 'B'
|
155
|
+
respond_to?(:window?).should be_true
|
156
|
+
respond_to?(:is_window).should be_true
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'adds Rubyesque alias to GetXxx API getter function' do
|
160
|
+
MyLib.function 'GetComputerName', 'PP', 'I', :dll=> 'kernel32'
|
161
|
+
respond_to?(:get_computer_name).should be_true
|
162
|
+
respond_to?(:computer_name).should be_true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'auto-defining Ruby-like boolean methods if API function name starts with "Is_"' do
|
167
|
+
before(:each) {MyLib.function 'IsWindow', 'L', 'L'}
|
168
|
+
|
169
|
+
it 'defines new instance method name dropping Is_ and adding ?' do
|
170
|
+
respond_to?(:window?).should be_true
|
171
|
+
respond_to?(:is_window).should be_true
|
172
|
+
respond_to?(:IsWindow).should be_true
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'defined CamelCase method returns zero/non-zero as expected' do
|
176
|
+
IsWindow(any_handle).should_not == true
|
177
|
+
IsWindow(any_handle).should_not == 0
|
178
|
+
IsWindow(not_a_handle).should == 0
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'defined snake_case method returns false/true instead of zero/non-zero' do
|
182
|
+
window?(any_handle).should == true
|
183
|
+
window?(not_a_handle).should == false
|
184
|
+
is_window(any_handle).should == true
|
185
|
+
is_window(not_a_handle).should == false
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'defined methods enforce the argument count' do
|
189
|
+
should_count_args :window?, :is_window, :IsWindow, [not_a_handle], [nil, not_a_handle, any_handle]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'defining API with :boolean option converts result to boolean' do
|
194
|
+
before(:each) { MyLib.function 'FindWindow', 'PP', 'L', :boolean => true }
|
195
|
+
|
196
|
+
it 'defines new instance method' do
|
197
|
+
respond_to?(:find_window).should be_true
|
198
|
+
respond_to?(:FindWindow).should be_true
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'defined snake_case method returns false/true instead of zero/non-zero' do
|
202
|
+
find_window(nil, nil).should == true
|
203
|
+
find_window(nil, TEST_IMPOSSIBLE).should == false
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'defined CamelCase method still returns zero/non-zero' do
|
207
|
+
FindWindow(nil, nil).should_not == true
|
208
|
+
FindWindow(nil, nil).should_not == 0
|
209
|
+
FindWindow(nil, TEST_IMPOSSIBLE).should == 0
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'defined methods enforce the argument count' do
|
213
|
+
should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'defining API with :zeronil option converts zero result to nil' do
|
218
|
+
before(:each) {MyLib.function 'FindWindow', 'PP', 'L', :zeronil => true}
|
219
|
+
|
220
|
+
it 'defines new instance method' do
|
221
|
+
respond_to?(:find_window).should be_true
|
222
|
+
respond_to?(:FindWindow).should be_true
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'defined CamelCase method still returns zero/non-zero' do
|
226
|
+
FindWindow(nil, nil).should_not == true
|
227
|
+
FindWindow(nil, nil).should_not == 0
|
228
|
+
FindWindow(nil, TEST_IMPOSSIBLE).should == 0
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'defined method returns nil (but NOT false) instead of zero' do
|
232
|
+
find_window(nil, TEST_IMPOSSIBLE).should_not == false
|
233
|
+
find_window(nil, TEST_IMPOSSIBLE).should == nil
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'defined method does not return true when result is non-zero' do
|
237
|
+
find_window(nil, nil).should_not == true
|
238
|
+
find_window(nil, nil).should_not == 0
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'defined methods enforce the argument count' do
|
242
|
+
should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
context 'using DLL other than default user32 with :dll option' do
|
247
|
+
before(:each) {MyLib.function 'GetComputerName', 'PP', 'I', :dll=> 'kernel32'}
|
248
|
+
|
249
|
+
it 'defines new instance method with appropriate name' do
|
250
|
+
respond_to?(:GetComputerName).should be_true
|
251
|
+
respond_to?(:get_computer_name).should be_true
|
252
|
+
respond_to?(:computer_name).should be_true
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'returns expected result' do
|
256
|
+
MyLib.function 'GetComputerName', ['P', 'P'], 'I', :dll=> 'kernel32'
|
257
|
+
hostname = `hostname`.strip.upcase
|
258
|
+
name = " " * 128
|
259
|
+
get_computer_name(name, "128")
|
260
|
+
name.unpack("A*").first.should == hostname
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
context 'trying to define an invalid API function' do
|
265
|
+
it 'raises error when trying to define function with a wrong function name' do
|
266
|
+
expect { MyLib.function 'FindWindowImpossible', 'PP', 'L' }.
|
267
|
+
to raise_error( /Function 'FindWindowImpossible' not found/ )
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context 'defining API function using definition block' do
|
272
|
+
it 'defines new instance method' do
|
273
|
+
MyLib.function( 'FindWindow', 'PP', 'L' ){|api, *args|}
|
274
|
+
respond_to?(:find_window).should be_true
|
275
|
+
respond_to?(:FindWindow).should be_true
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'does not enforce argument count outside of block' do
|
279
|
+
MyLib.function( 'FindWindow', 'PP', 'L' ){|api, *args|}
|
280
|
+
expect { find_window }.to_not raise_error
|
281
|
+
expect { find_window(nil) }.to_not raise_error
|
282
|
+
expect { find_window(nil, 'Str', 1) }.to_not raise_error
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'returns block return value when defined method is called' do
|
286
|
+
MyLib.function( 'FindWindow', 'PP', 'L' ){|api, *args| 'Value'}
|
287
|
+
find_window(nil).should == 'Value'
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'passes arguments and underlying Win32::API object to the block' do
|
291
|
+
MyLib.function( 'FindWindow', 'PP', 'L' ) do |api, *args|
|
292
|
+
@api = api
|
293
|
+
@args = args
|
294
|
+
end
|
295
|
+
expect {find_window(1, 2, 3) }.to_not raise_error
|
296
|
+
@args.should == [1, 2, 3]
|
297
|
+
should_be :find_window, @api
|
298
|
+
end
|
299
|
+
|
300
|
+
it ':rename option overrides standard name for defined method' do
|
301
|
+
MyLib.function( 'FindWindow', 'PP', 'L', :rename => 'my_own_find' ){|api, *args|}
|
302
|
+
expect {find_window(nil, nil, nil)}.to raise_error
|
303
|
+
expect {my_own_find(nil, nil)}.to_not raise_error
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'adds alias for defined method with :alias option' do
|
307
|
+
MyLib.function( 'FindWindow', 'PP', 'L', :alias => 'my_own_find' ){|api, *args|}
|
308
|
+
expect {find_window(nil, nil)}.to_not raise_error
|
309
|
+
expect {my_own_find(nil, nil)}.to_not raise_error
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'adds aliases for defined method with :aliases option' do
|
313
|
+
MyLib.function( 'FindWindow', 'PP', 'L', :aliases => ['my_own_find', 'my_own_find1'] ) {|api, *args|}
|
314
|
+
expect {find_window(nil, nil)}.to_not raise_error
|
315
|
+
expect {my_own_find(nil, nil)}.to_not raise_error
|
316
|
+
expect {my_own_find1(nil, nil)}.to_not raise_error
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
context 'calling defined methods with attached block to preprocess the API function results' do
|
321
|
+
it 'defined method yields raw result to block attached to its invocation' do
|
322
|
+
MyLib.function 'FindWindow', 'PP', 'L', zeronil: true
|
323
|
+
find_window(nil, TEST_IMPOSSIBLE) {|result| result.should == 0 }
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'defined method returns result of block attached to its invocation' do
|
327
|
+
MyLib.function 'FindWindow', 'PP', 'L', zeronil: true
|
328
|
+
return_value = find_window(nil, TEST_IMPOSSIBLE) {|result| 'Value'}
|
329
|
+
return_value.should == 'Value'
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'defined method transforms result of block before returning it' do
|
333
|
+
MyLib.function 'FindWindow', 'PP', 'L', zeronil: true
|
334
|
+
return_value = find_window(nil, TEST_IMPOSSIBLE) {|result| 0 }
|
335
|
+
return_value.should_not == 0
|
336
|
+
return_value.should == nil
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
context 'defining API function without arguments - f(VOID)' do
|
341
|
+
it 'should enforce argument count to 0, NOT 1' do
|
342
|
+
MyLib.function 'GetForegroundWindow', [], 'L', zeronil: true
|
343
|
+
should_count_args :GetForegroundWindow, :get_foreground_window, :foreground_window, [], [nil, 0, 123]
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
context 'working with API function callbacks' do
|
348
|
+
it '#callback method creates a valid callback object' do
|
349
|
+
pending 'What about callbacks?'
|
350
|
+
expect { @callback = WinGui.callback('LP', 'I') {|handle, message| true} }.to_not raise_error
|
351
|
+
@callback.should be_a_kind_of(Win32::API::Callback)
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'created callback object can be used as a valid arg of API function expecting callback' do
|
355
|
+
pending 'What about callbacks?'
|
356
|
+
MyLib.function 'EnumWindows', 'KP', 'L'
|
357
|
+
@callback = WinGui.callback('LP', 'I'){|handle, message| true }
|
358
|
+
expect { enum_windows(@callback, 'Message') }.to_not raise_error
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'defined API functions expecting callback convert given block into callback' do
|
362
|
+
pending 'What about callbacks?'
|
363
|
+
pending ' What about prototype!? API is not exactly clear atm (.with_callback method?)'
|
364
|
+
MyLib.function 'EnumWindows', 'KP', 'L'
|
365
|
+
expect { enum_windows('Message'){|handle, message| true } }.to_not raise_error
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
end
|
370
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: win
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- arvicco
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-08 00:00:00 +03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: cucumber
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: A collection of Windows functions predefined for you using FFI
|
36
|
+
email: arvitallian@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.rdoc
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- features/step_definitions/win_steps.rb
|
52
|
+
- features/support/env.rb
|
53
|
+
- features/win.feature
|
54
|
+
- lib/win/extensions.rb
|
55
|
+
- lib/win/library.rb
|
56
|
+
- spec/spec.opts
|
57
|
+
- spec/spec_helper.rb
|
58
|
+
- spec/win/extensions_spec.rb
|
59
|
+
- spec/win/library_spec.rb
|
60
|
+
has_rdoc: true
|
61
|
+
homepage: http://github.com/arvicco/win
|
62
|
+
licenses: []
|
63
|
+
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options:
|
66
|
+
- --charset=UTF-8
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.3.5
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: A collection of Windows functions predefined for you using FFI
|
88
|
+
test_files:
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
- spec/win/extensions_spec.rb
|
91
|
+
- spec/win/library_spec.rb
|