win32screenshot 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: