win32screenshot 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +11 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/win32/screenshot/bitmap_maker.rb +35 -7
- data/lib/win32/util.rb +55 -2
- data/spec/win32_screenshot_spec.rb +17 -4
- data/spec/win32_screenshot_util_spec.rb +40 -3
- data/win32screenshot.gemspec +5 -5
- metadata +5 -5
data/History.rdoc
CHANGED
@@ -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
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
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], :
|
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
|
-
|
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
|
-
|
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)
|
data/lib/win32/util.rb
CHANGED
@@ -3,7 +3,7 @@ module Win32
|
|
3
3
|
class Util
|
4
4
|
class << self
|
5
5
|
|
6
|
-
def
|
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
|
-
|
178
|
-
|
179
|
-
|
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 ".
|
13
|
-
all_windows = Win32::Screenshot::Util.
|
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
|
-
|
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
|
data/win32screenshot.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{win32screenshot}
|
8
|
-
s.version = "0.0.
|
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-
|
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>, ["
|
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>, ["
|
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>, ["
|
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:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
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-
|
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:
|