win 0.0.2
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/.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
|