win32screenshot 0.0.7 → 0.0.8

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.
@@ -1,3 +1,14 @@
1
+ = 0.0.8 2010-12-13
2
+ * Renamed Win32::Screenshot::Util.all_windows to all_desktop_windows (Roger Pack)
3
+ * Added methods to Win32::Screenshot::Util class (Roger Pack):
4
+ - window_process_id
5
+ - window_class
6
+ - windows_hierarchy_with_info
7
+ - get_info
8
+ - location_of
9
+ * It's possible to search windows also by class name (Roger Pack)
10
+ * Child windows will be also searched for (Roger Pack)
11
+
1
12
  = 0.0.7 2010-08-18
2
13
  * Supports now fully JRuby, 1.9.1 and 1.9.2 MRI!
3
14
 
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ begin
16
16
 
17
17
  gem.rdoc_options = ["--main", "README.rdoc"]
18
18
 
19
- gem.add_dependency "ffi"
19
+ gem.add_dependency "ffi", "~>0"
20
20
 
21
21
  gem.add_development_dependency "rspec", ">= 1.2.9"
22
22
  gem.add_development_dependency 'os'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.7
1
+ 0.0.8
@@ -13,17 +13,23 @@ module Win32
13
13
 
14
14
  # user32.dll
15
15
  attach_function :enum_windows, :EnumWindows,
16
- [:enum_callback, :pointer], :long
16
+ [:enum_callback, :pointer], :int
17
+ attach_function :enum_child_windows, :EnumChildWindows,
18
+ [:long, :enum_callback, :pointer], :int
17
19
  attach_function :window_text, :GetWindowTextA,
18
20
  [:long, :pointer, :int], :int
19
21
  attach_function :window_text_length, :GetWindowTextLengthA,
20
22
  [:long], :int
23
+ attach_function :class_name, :GetClassNameA,
24
+ [:long, :pointer, :int], :int
21
25
  attach_function :window_visible, :IsWindowVisible,
22
26
  [:long], :bool
23
27
  attach_function :dc, :GetDC,
24
28
  [:long], :long
25
29
  attach_function :client_rect, :GetClientRect,
26
30
  [:long, :pointer], :bool
31
+ attach_function :window_rect, :GetWindowRect,
32
+ [:long, :pointer], :bool
27
33
  attach_function :minimized, :IsIconic,
28
34
  [:long], :bool
29
35
  attach_function :show_window, :ShowWindow,
@@ -43,7 +49,6 @@ module Win32
43
49
  attach_function :set_active_window, :SetActiveWindow,
44
50
  [:long], :long
45
51
 
46
-
47
52
  # gdi32.dll
48
53
  attach_function :create_compatible_dc, :CreateCompatibleDC,
49
54
  [:long], :long
@@ -65,21 +70,37 @@ module Win32
65
70
 
66
71
  EnumWindowCallback = FFI::Function.new(:bool, [ :long, :pointer ], { :convention => :stdcall }) do |hwnd, param|
67
72
  searched_window = WindowStruct.new param
68
- title = Util.window_title(hwnd)
73
+ if(searched_window[:search_class] != 0)
74
+ title = Util.window_class(hwnd)
75
+ else
76
+ title = Util.window_title(hwnd)
77
+ end
69
78
  if title =~ Regexp.new(searched_window[:title].read_string) && window_visible(hwnd)
70
79
  searched_window[:hwnd] = hwnd
71
80
  false
72
81
  else
73
- true
82
+ if(searched_window[:search_class] != 0)
83
+ # if they're searching for a classname, enumerate children, too
84
+ enum_child_windows(hwnd, EnumWindowCallback, param)
85
+ if searched_window[:hwnd] != 0
86
+ # return early if already discovered
87
+ false
88
+ else
89
+ true
90
+ end
91
+ else
92
+ true
93
+ end
74
94
  end
75
95
  end
76
96
 
77
97
  class WindowStruct < FFI::Struct
78
98
  layout :title, :pointer,
79
- :hwnd, :long
99
+ :hwnd, :long,
100
+ :search_class, :char # boolean
80
101
  end
81
102
 
82
- def hwnd(window_title)
103
+ def hwnd(window_title, search_class = false)
83
104
  window = WindowStruct.new
84
105
  unless window_title.is_a?(Regexp)
85
106
  window_title = Regexp.escape(window_title.to_s)
@@ -88,10 +109,11 @@ module Win32
88
109
  end
89
110
  window_title = FFI::MemoryPointer.from_string(window_title)
90
111
  window[:title] = window_title
112
+ window[:search_class] = search_class ? 1 : 0
91
113
  enum_windows(EnumWindowCallback, window.to_ptr)
92
114
  window[:hwnd] == 0 ? nil : window[:hwnd]
93
115
  end
94
-
116
+
95
117
  def prepare_window(hwnd, pause)
96
118
  restore(hwnd) if minimized(hwnd)
97
119
  set_foreground(hwnd)
@@ -119,6 +141,12 @@ module Win32
119
141
  attach_thread_input(foreground_thread, other_thread, false) unless other_thread == foreground_thread
120
142
  end
121
143
  end
144
+
145
+ def get_process_id_from_hwnd hwnd
146
+ out = FFI::MemoryPointer.new(:uint)
147
+ window_thread_process_id(hwnd, out)
148
+ out.get_uint32(0) # read_uint
149
+ end
122
150
 
123
151
  def capture_all(hwnd, &proc)
124
152
  width, height = Util.dimensions_for(hwnd)
@@ -3,7 +3,7 @@ module Win32
3
3
  class Util
4
4
  class << self
5
5
 
6
- def all_windows
6
+ def all_desktop_windows
7
7
  titles = []
8
8
  window_callback = FFI::Function.new(:bool, [ :long, :pointer ], { :convention => :stdcall }) do |hwnd, param|
9
9
  titles << [window_title(hwnd), hwnd]
@@ -13,19 +13,68 @@ module Win32
13
13
  BitmapMaker.enum_windows(window_callback, nil)
14
14
  titles
15
15
  end
16
-
16
+
17
+ # just returns a long list of hwnd's...
18
+ # unless with_info is true
19
+ # then it will return all hwnds with full info about each window
20
+ def windows_hierarchy with_info = false
21
+ all = {}
22
+ desktop_hwnd = BitmapMaker.desktop_window
23
+ root = {:hwnd => desktop_hwnd, :children => []}
24
+ root.merge!(get_info(desktop_hwnd)) if with_info
25
+ parents = []
26
+ parents << root
27
+ window_callback = FFI::Function.new(:bool, [ :long, :pointer ], { :convention => :stdcall }) do |hwnd, param|
28
+ # this is a child of the most recent parent
29
+ myself = {:hwnd => hwnd, :children => []}
30
+ myself.merge!(get_info(hwnd)) if with_info
31
+ parents[-1][:children] << myself
32
+ parents << myself
33
+ if !all[hwnd]
34
+ all[hwnd] = true
35
+ BitmapMaker.enum_child_windows(hwnd, window_callback, nil)
36
+ end
37
+
38
+ parents.pop
39
+ true
40
+ end
41
+ BitmapMaker.enum_child_windows(desktop_hwnd, window_callback, nil)
42
+ root
43
+ end
44
+
45
+ def get_info hwnd
46
+ {:title => window_title(hwnd),
47
+ :class => window_class(hwnd),
48
+ :dimensions => dimensions_for(hwnd),
49
+ :starting_coordinates => location_of(hwnd)
50
+ }
51
+ end
52
+
17
53
  def window_title hwnd
18
54
  title_length = BitmapMaker.window_text_length(hwnd) + 1
19
55
  title = FFI::MemoryPointer.new :char, title_length
20
56
  BitmapMaker.window_text(hwnd, title, title_length)
21
57
  title.read_string
22
58
  end
59
+
60
+ def window_class hwnd
61
+ title = FFI::MemoryPointer.new :char, 100
62
+ BitmapMaker.class_name(hwnd, title, 99)
63
+ title.read_string
64
+ end
23
65
 
24
66
  def window_hwnd(title_query)
25
67
  hwnd = BitmapMaker.hwnd(title_query)
26
68
  raise "window with title '#{title_query}' was not found!" unless hwnd
27
69
  hwnd
28
70
  end
71
+
72
+ def location_of(hwnd)
73
+ rect = [0, 0, 0, 0].pack('L4')
74
+ BitmapMaker.window_rect(hwnd, rect)
75
+ x, y, width, height = rect.unpack('L4')
76
+ return x, y
77
+ end
29
78
 
30
79
  def dimensions_for(hwnd)
31
80
  rect = [0, 0, 0, 0].pack('L4')
@@ -33,6 +82,10 @@ module Win32
33
82
  _, _, width, height = rect.unpack('L4')
34
83
  return width, height
35
84
  end
85
+
86
+ def window_process_id(hwnd)
87
+ BitmapMaker.get_process_id_from_hwnd(hwnd)
88
+ end
36
89
 
37
90
  end
38
91
  end
@@ -72,6 +72,17 @@ describe Win32::Screenshot do
72
72
  [width, height].should == Win32::Screenshot::Util.dimensions_for(hwnd)
73
73
  end
74
74
  end
75
+
76
+ it "can also search by window class name" do
77
+ good_pid = Win32::Screenshot::BitmapMaker.hwnd(/calculator/i)
78
+ good_pid.should_not be_nil
79
+ good_pid.should == Win32::Screenshot::BitmapMaker.hwnd(/calcframe/i, true)
80
+ end
81
+
82
+ it "can find sub windows as well" do
83
+ # search for an IE sub-window by class (doesn't have text)
84
+ Win32::Screenshot::BitmapMaker.hwnd(/CommandBarClass/i, true).should_not be_nil
85
+ end
75
86
 
76
87
  it "captures small windows" do
77
88
  title = /Notepad/
@@ -140,7 +151,7 @@ describe Win32::Screenshot do
140
151
  [width, height].should == Win32::Screenshot::Util.dimensions_for(hwnd)
141
152
  end
142
153
  end
143
-
154
+
144
155
  it "captures area of the window with handle" do
145
156
  hwnd = Win32::Screenshot::BitmapMaker.hwnd(/calculator/i)
146
157
  Win32::Screenshot.hwnd_area(hwnd, 30, 30, 100, 150) do |width, height, bmp|
@@ -174,8 +185,10 @@ describe Win32::Screenshot do
174
185
  end
175
186
 
176
187
  after :all do
177
- Process.kill 9, @notepad
178
- Process.kill 9, @iexplore rescue nil # allow for a pre-existing IE to have been used.
179
- Process.kill 9, @calc
188
+ for name in [/calculator/i, /Notepad/, /Internet Explorer/] do
189
+ # kill them in a jruby friendly way
190
+ pid = Win32::Screenshot::Util.window_process_id(Win32::Screenshot::Util.window_hwnd(name))
191
+ system("taskkill /PID #{pid}")
192
+ end
180
193
  end
181
194
  end
@@ -4,13 +4,15 @@ describe Win32::Screenshot::Util do
4
4
  include SpecHelper
5
5
 
6
6
  before :all do
7
+ # should not have any running calculators yet...
8
+ proc {Win32::Screenshot::Util.window_hwnd("Calculator") }.should raise_exception("window with title 'Calculator' was not found!")
7
9
  @calc = IO.popen("calc").pid
8
10
  wait_for_calculator_to_open
9
11
  @calc_hwnd = Win32::Screenshot::Util.window_hwnd("Calculator")
10
12
  end
11
13
 
12
- it ".all_windows enumerates all available windows" do
13
- all_windows = Win32::Screenshot::Util.all_windows
14
+ it ".all_desktop_windows enumerates all available windows" do
15
+ all_windows = Win32::Screenshot::Util.all_desktop_windows
14
16
  all_windows.should_not be_empty
15
17
  all_windows[0].should be_an(Array)
16
18
  all_windows[0][0].should be_a(String)
@@ -32,7 +34,42 @@ describe Win32::Screenshot::Util do
32
34
  height.should be > 100
33
35
  end
34
36
 
37
+ it ".window_class returns classname of a specified window's handle" do
38
+ Win32::Screenshot::Util.window_class(@calc_hwnd).should == "CalcFrame"
39
+ end
40
+
41
+ it ".get_info returns lots of info about an hwnd" do
42
+ desktop_hwnd = Win32::Screenshot::BitmapMaker.desktop_window
43
+ info = Win32::Screenshot::Util.get_info desktop_hwnd
44
+ info.should be_a(Hash)
45
+ info.keys.map {|k| k.to_s}.sort.should == ["class", "dimensions", "starting_coordinates", "title"]
46
+ end
47
+
48
+ it ".windows_hierarchy returns hwnds" do
49
+ a = Win32::Screenshot::Util.windows_hierarchy
50
+ # should have root as "desktop"
51
+ # though in reality some windows might not be descendants of the desktop
52
+ # (see the WinCheat source which discusses this further)
53
+ # but we don't worry about that edge case yet
54
+ a.should be_a(Hash)
55
+ a[:children].should be_an(Array)
56
+ a[:children].length.should be > 0
57
+ a[:children][0].should be_a(Hash)
58
+ a[:hwnd].should == Win32::Screenshot::BitmapMaker.desktop_window
59
+ end
60
+
61
+ it ".windows_hierarchy can return info" do
62
+ a = Win32::Screenshot::Util.windows_hierarchy true
63
+ # check for right structure
64
+ for hash_example in [a, a[:children][0]] do
65
+ hash_example.keys.map {|k| k.to_s}.sort.should == ["children", "class", "dimensions", "hwnd", "starting_coordinates", "title"]
66
+ end
67
+ end
68
+
35
69
  after :all do
36
- Process.kill 9, @calc
70
+ # tests our hwnd -> pid method, and conveniently, shuts down the calculator process
71
+ calc_pid = Win32::Screenshot::Util.window_process_id(@calc_hwnd)
72
+ system("taskkill /PID #{calc_pid}")
73
+ proc {Win32::Screenshot::Util.window_hwnd("Calculator") }.should raise_exception("window with title 'Calculator' was not found!")
37
74
  end
38
75
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{win32screenshot}
8
- s.version = "0.0.7"
8
+ s.version = "0.0.8"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jarmo Pertman", "Aslak Helles\303\270y"]
12
- s.date = %q{2010-08-18}
12
+ s.date = %q{2010-12-13}
13
13
  s.description = %q{Capture Screenshots on Windows with Ruby}
14
14
  s.email = ["jarmo.p@gmail.com", "aslak.hellesoy@gmail.com"]
15
15
  s.extra_rdoc_files = [
@@ -49,18 +49,18 @@ Gem::Specification.new do |s|
49
49
  s.specification_version = 3
50
50
 
51
51
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
- s.add_runtime_dependency(%q<ffi>, [">= 0"])
52
+ s.add_runtime_dependency(%q<ffi>, ["~> 0"])
53
53
  s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
54
54
  s.add_development_dependency(%q<os>, [">= 0"])
55
55
  s.add_development_dependency(%q<rmagick>, [">= 0"])
56
56
  else
57
- s.add_dependency(%q<ffi>, [">= 0"])
57
+ s.add_dependency(%q<ffi>, ["~> 0"])
58
58
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
59
59
  s.add_dependency(%q<os>, [">= 0"])
60
60
  s.add_dependency(%q<rmagick>, [">= 0"])
61
61
  end
62
62
  else
63
- s.add_dependency(%q<ffi>, [">= 0"])
63
+ s.add_dependency(%q<ffi>, ["~> 0"])
64
64
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
65
65
  s.add_dependency(%q<os>, [">= 0"])
66
66
  s.add_dependency(%q<rmagick>, [">= 0"])
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: win32screenshot
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 7
10
- version: 0.0.7
9
+ - 8
10
+ version: 0.0.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jarmo Pertman
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-08-18 00:00:00 +03:00
19
+ date: 2010-12-13 00:00:00 +02:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -25,7 +25,7 @@ dependencies:
25
25
  requirement: &id001 !ruby/object:Gem::Requirement
26
26
  none: false
27
27
  requirements:
28
- - - ">="
28
+ - - ~>
29
29
  - !ruby/object:Gem::Version
30
30
  hash: 3
31
31
  segments: