win_gui 0.1.2 → 0.1.3

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