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.
- 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:
|