win 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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,4 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'win'
3
+
4
+ require 'spec/expectations'
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format nested
@@ -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