win32screenshot 2.1.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,100 +1,121 @@
1
- module Win32
2
- module Screenshot
3
- # @private
4
- # This is an internal class for taking the actual screenshots and not part of a public API.
5
- class BitmapMaker
6
- class << self
7
- extend FFI::Library
8
-
9
- ffi_lib 'user32', 'gdi32'
10
- ffi_convention :stdcall
11
-
12
- # user32.dll
13
- attach_function :window_dc, :GetWindowDC,
14
- [:long], :long
15
- attach_function :client_dc, :GetDC,
16
- [:long], :long
17
- attach_function :client_rect, :GetClientRect,
18
- [:long, :pointer], :bool
19
- attach_function :window_rect, :GetWindowRect,
20
- [:long, :pointer], :bool
21
- attach_function :foreground_window, :GetForegroundWindow,
22
- [], :long
23
- attach_function :desktop_window, :GetDesktopWindow,
24
- [], :long
25
-
26
- # gdi32.dll
27
- attach_function :create_compatible_dc, :CreateCompatibleDC,
28
- [:long], :long
29
- attach_function :create_compatible_bitmap, :CreateCompatibleBitmap,
30
- [:long, :int, :int], :long
31
- attach_function :select_object, :SelectObject,
32
- [:long, :long], :long
33
- attach_function :bit_blt, :BitBlt,
34
- [:long, :int, :int, :int, :int, :long, :int, :int, :long], :bool
35
- attach_function :di_bits, :GetDIBits,
36
- [:long, :long, :int, :int, :pointer, :pointer, :int], :int
37
- attach_function :delete_object, :DeleteObject,
38
- [:long], :bool
39
- attach_function :delete_dc, :DeleteDC,
40
- [:long], :bool
41
- attach_function :release_dc, :ReleaseDC,
42
- [:long, :long], :int
43
-
44
- def capture_all(hwnd, context)
45
- width, height = dimensions_for(hwnd, context)
46
- capture_area(hwnd, context, 0, 0, width, height)
47
- end
48
-
49
- SRCCOPY = 0x00CC0020
50
- DIB_RGB_COLORS = 0
51
-
52
- def capture_area(hwnd, context, x1, y1, x2, y2)
53
- hScreenDC = send("#{context}_dc", hwnd)
54
- w = x2-x1
55
- h = y2-y1
56
-
57
- hmemDC = create_compatible_dc(hScreenDC)
58
- hmemBM = create_compatible_bitmap(hScreenDC, w, h)
59
- select_object(hmemDC, hmemBM)
60
- bit_blt(hmemDC, 0, 0, w, h, hScreenDC, x1, y1, SRCCOPY)
61
- bitmap_size = w * h * 3 + w % 4 * h
62
- lpvpxldata = FFI::MemoryPointer.new(bitmap_size)
63
-
64
- # Bitmap header
65
- # http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html
66
- bmInfo = [40, w, h, 1, 24, 0, 0, 0, 0, 0, 0, 0].pack('L3S2L6')
67
- di_bits(hmemDC, hmemBM, 0, h, lpvpxldata, bmInfo, DIB_RGB_COLORS)
68
-
69
- bmFileHeader = [
70
- 19778,
71
- bitmap_size + 40 + 14,
72
- 0,
73
- 0,
74
- 54
75
- ].pack('SLSSL')
76
-
77
- Image.new(bmFileHeader + bmInfo + lpvpxldata.read_string(bitmap_size), w, h)
78
- ensure
79
- lpvpxldata.free
80
- delete_object(hmemBM)
81
- delete_dc(hmemDC)
82
- release_dc(0, hScreenDC)
83
- end
84
-
85
- def dimensions_for(hwnd, context)
86
- rect = [0, 0, 0, 0].pack('l4')
87
- BitmapMaker.send("#{context}_rect", hwnd.to_i, rect)
88
- left, top, width, height = rect.unpack('l4')
89
-
90
- if context == :window
91
- [width + 1 - left, height + 1 - top]
92
- else
93
- [width, height]
94
- end
95
- end
96
-
97
- end
98
- end
99
- end
100
- end
1
+ module Win32
2
+ module Screenshot
3
+ # @private
4
+ # This is an internal class for taking the actual screenshots and not part of a public API.
5
+ class BitmapMaker
6
+ class << self
7
+ extend FFI::Library
8
+
9
+ ffi_lib 'user32', 'gdi32'
10
+ ffi_convention :stdcall
11
+
12
+ # user32.dll
13
+ attach_function :window_dc, :GetWindowDC,
14
+ [:long], :long
15
+ attach_function :window_rect, :GetWindowRect,
16
+ [:long, :pointer], :bool
17
+ attach_function :foreground_window, :GetForegroundWindow,
18
+ [], :long
19
+ attach_function :desktop_window, :GetDesktopWindow,
20
+ [], :long
21
+ attach_function :print_window, :PrintWindow,
22
+ [:long, :long, :int], :bool
23
+ attach_function :get_system_metrics, :GetSystemMetrics,
24
+ [:int], :int
25
+
26
+ # gdi32.dll
27
+ attach_function :create_compatible_dc, :CreateCompatibleDC,
28
+ [:long], :long
29
+ attach_function :create_compatible_bitmap, :CreateCompatibleBitmap,
30
+ [:long, :int, :int], :long
31
+ attach_function :select_object, :SelectObject,
32
+ [:long, :long], :long
33
+ attach_function :di_bits, :GetDIBits,
34
+ [:long, :long, :int, :int, :pointer, :pointer, :int], :int
35
+ attach_function :delete_object, :DeleteObject,
36
+ [:long], :bool
37
+ attach_function :delete_dc, :DeleteDC,
38
+ [:long], :bool
39
+ attach_function :release_dc, :ReleaseDC,
40
+ [:long, :long], :int
41
+ attach_function :bit_blt, :BitBlt,
42
+ [:long, :int, :int, :int, :int, :long, :int, :int, :long], :bool
43
+
44
+ DIB_RGB_COLORS = 0
45
+ PW_RENDERFULLCONTENT = 0x00000002
46
+ SRCCOPY = 0x00CC0020
47
+
48
+ SM_XVIRTUALSCREEN = 76
49
+ SM_YVIRTUALSCREEN = 77
50
+ SM_CXVIRTUALSCREEN = 78
51
+ SM_CYVIRTUALSCREEN = 79
52
+
53
+ def capture_window(hwnd)
54
+ width, height = dimensions_for(hwnd)
55
+
56
+ hScreenDC, hmemDC, hmemBM = prepare_object(hwnd, width, height)
57
+ print_window(hwnd, hmemDC, PW_RENDERFULLCONTENT)
58
+ create_bitmap(hScreenDC, hmemDC, hmemBM, width, height)
59
+ end
60
+
61
+ def capture_screen(hwnd)
62
+ left, top, width, height = desktop.dimensions
63
+
64
+ hScreenDC, hmemDC, hmemBM = prepare_object(hwnd, width, height)
65
+ bit_blt(hmemDC, 0, 0, width, height, hScreenDC, left, top, SRCCOPY)
66
+ create_bitmap(hScreenDC, hmemDC, hmemBM, width, height)
67
+ end
68
+
69
+ def prepare_object(hwnd, width, height)
70
+ hScreenDC = window_dc(hwnd)
71
+ hmemDC = create_compatible_dc(hScreenDC)
72
+ hmemBM = create_compatible_bitmap(hScreenDC, width, height)
73
+ select_object(hmemDC, hmemBM)
74
+ [hScreenDC, hmemDC, hmemBM]
75
+ end
76
+
77
+ def create_bitmap(hScreenDC, hmemDC, hmemBM, width, height)
78
+ bitmap_size = width * height * 3 + width % 4 * height
79
+ lpvpxldata = FFI::MemoryPointer.new(bitmap_size)
80
+
81
+ # Bitmap header
82
+ # http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html
83
+ bmInfo = [40, width, height, 1, 24, 0, 0, 0, 0, 0, 0, 0].pack('L3S2L6')
84
+ di_bits(hmemDC, hmemBM, 0, height, lpvpxldata, bmInfo, DIB_RGB_COLORS)
85
+
86
+ bmFileHeader = [
87
+ 19778,
88
+ bitmap_size + 40 + 14,
89
+ 0,
90
+ 0,
91
+ 54
92
+ ].pack('SLSSL')
93
+
94
+ Image.new(bmFileHeader + bmInfo + lpvpxldata.read_string(bitmap_size), width, height)
95
+ ensure
96
+ lpvpxldata.free
97
+ delete_object(hmemBM)
98
+ delete_dc(hmemDC)
99
+ release_dc(0, hScreenDC)
100
+ end
101
+
102
+ def desktop
103
+ Win32::Screenshot::Desktop.new(
104
+ get_system_metrics(SM_XVIRTUALSCREEN),
105
+ get_system_metrics(SM_YVIRTUALSCREEN),
106
+ get_system_metrics(SM_CXVIRTUALSCREEN),
107
+ get_system_metrics(SM_CYVIRTUALSCREEN)
108
+ )
109
+ end
110
+
111
+ def dimensions_for(hwnd)
112
+ rect = [0, 0, 0, 0].pack('l4')
113
+ window_rect(hwnd.to_i, rect)
114
+ left, top, width, height = rect.unpack('l4')
115
+
116
+ [width - left, height - top]
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,5 @@
1
+ Win32::Screenshot::Desktop = Struct.new(:left, :top, :width, :height) do
2
+ def dimensions
3
+ [left, top, width, height]
4
+ end
5
+ end
@@ -1,109 +1,79 @@
1
- module Win32
2
- module Screenshot
3
- # Capture Screenshots on Windows with Ruby
4
- class Take
5
-
6
- class << self
7
- # Takes a screenshot of the specified object or it's area.
8
- #
9
- # @example Take a screenshot of the window with the specified title
10
- # Win32::Screenshot::Take.of(:window, :title => "Windows Internet Explorer")
11
- #
12
- # @example Take a screenshot of the foreground
13
- # Win32::Screenshot::Take.of(:foreground)
14
- #
15
- # @example Take a screenshot of the specified window's top-left corner's area
16
- # Win32::Screenshot::Take.of(:window, :title => /internet/i, :area => [10, 10, 20, 20])
17
- #
18
- # @example Take a screenshot of the window with the specified handle
19
- # Win32::Screenshot::Take.of(:window, :hwnd => 123456)
20
- #
21
- # @example Take a screenshot of the window's client area (e.g. without title bar) with the specified handle
22
- # Win32::Screenshot::Take.of(:window, :hwnd => 123456, :context => :client)
23
- #
24
- # @example Take a screenshot of the child window with the specified internal class name
25
- # Win32::Screenshot::Take.of(:rautomation, RAutomation::Window.new(:hwnd => 123456).child(:class => "Internet Explorer_Server"))
26
- #
27
- # @param [Symbol] what the type of the object to take a screenshot of,
28
- # possible values are _:foreground_, _:desktop_ and _:window_.
29
- # @param [Hash] opts options are optional for specifying an _:area_ and/or _:context_ to take a screenshot.
30
- # It is possible to specify as many options as are needed for searching for the unique window.
31
- # By default the first window with matching identifiers will be taken screenshot of.
32
- # It is possible to use in addition to other options a 0-based _:index_ option to search for other windows if multiple
33
- # windows match the specified criteria.
34
- # @option opts [String, Symbol] :context Context to take a screenshot of. Can be _:window_ or _:client_. Defaults to _:window_
35
- # @option opts [String, Regexp] :title Title of the window
36
- # @option opts [String, Regexp] :text Visible text of the window
37
- # @option opts [String, Regexp] :class Internal class name of the window
38
- # @option opts [String, Fixnum] :hwnd Window handle in decimal format
39
- # @option opts [String, Fixnum] :pid Window process ID (PID)
40
- # @option opts [String, Fixnum] :index 0-based index to specify n-th window to take a screenshot of if
41
- # all other criteria match
42
- # @option opts [RAutomation::Window] :rautomation RAutomation::Window object to take a screenshot of. Useful for
43
- # taking screenshots of the child windows
44
- # @return [Image] the {Image} of the specified object
45
- def of(what, opts = {})
46
- valid_whats = [:foreground, :desktop, :window]
47
- raise "It is not possible to take a screenshot of '#{what}', possible values are #{valid_whats.join(", ")}" unless valid_whats.include?(what)
48
-
49
- self.send(what, {:context => :window}.merge(opts))
50
- end
51
-
52
- alias_method :new, :of
53
-
54
- private
55
-
56
- def foreground(opts)
57
- hwnd = BitmapMaker.foreground_window
58
- take_screenshot(hwnd, opts)
59
- end
60
-
61
- def desktop(opts)
62
- hwnd = BitmapMaker.desktop_window
63
- take_screenshot(hwnd, opts)
64
- end
65
-
66
- def window(opts)
67
- area = {:area => opts.delete(:area)}
68
- context = {:context => opts.delete(:context)}
69
- win = opts[:rautomation] || RAutomation::Window.new(opts)
70
- timeout = Time.now + 10
71
- until win.active?
72
- if Time.now >= timeout
73
- Kernel.warn "[WARN] Failed to set window '#{win.locators.inspect}' into focus for taking the screenshot"
74
- break
75
- end
76
- win.activate
77
- end
78
- take_screenshot(win.hwnd, opts.merge(context).merge(area || {}))
79
- end
80
-
81
- def take_screenshot(hwnd, opts)
82
- if opts[:area]
83
- validate_coordinates(hwnd, opts[:context], *opts[:area])
84
- BitmapMaker.capture_area(hwnd, opts[:context], *opts[:area])
85
- else
86
- BitmapMaker.capture_all(hwnd, opts[:context])
87
- end
88
- end
89
-
90
- def validate_coordinates(hwnd, context, x1, y1, x2, y2)
91
- specified_coordinates = "x1: #{x1}, y1: #{y1}, x2: #{x2}, y2: #{y2}"
92
- if [x1, y1, x2, y2].any? {|c| c < 0}
93
- raise "specified coordinates (#{specified_coordinates}) are invalid - cannot be negative!"
94
- end
95
-
96
- if x1 >= x2 || y1 >= y2
97
- raise "specified coordinates (#{specified_coordinates}) are invalid - cannot be x1 >= x2 or y1 >= y2!"
98
- end
99
-
100
- max_width, max_height = BitmapMaker.dimensions_for(hwnd, context)
101
- if x2 > max_width || y2 > max_height
102
- raise "specified coordinates (#{specified_coordinates}) are invalid - maximum x2: #{max_width} and y2: #{max_height}!"
103
- end
104
- end
105
-
106
- end
107
- end
108
- end
109
- end
1
+ module Win32
2
+ module Screenshot
3
+ # Capture Screenshots on Windows with Ruby
4
+ class Take
5
+
6
+ class << self
7
+ # Takes a screenshot of the specified object.
8
+ #
9
+ # @example Take a screenshot of the window with the specified title
10
+ # Win32::Screenshot::Take.of(:window, :title => "Windows Internet Explorer")
11
+ #
12
+ # @example Take a screenshot of the foreground
13
+ # Win32::Screenshot::Take.of(:foreground)
14
+ #
15
+ # @example Take a screenshot of the desktop
16
+ # Win32::Screenshot::Take.of(:desktop)
17
+ #
18
+ # @example Take a screenshot of the window with the specified handle
19
+ # Win32::Screenshot::Take.of(:window, :hwnd => 123456)
20
+ #
21
+ # @example Take a screenshot of the window's client area (e.g. without title bar) with the specified handle
22
+ # Win32::Screenshot::Take.of(:window, :hwnd => 123456, :context => :client)
23
+ #
24
+ # @example Take a screenshot of the child window with the specified internal class name
25
+ # Win32::Screenshot::Take.of(:rautomation, RAutomation::Window.new(:hwnd => 123456).child(:class => "Internet Explorer_Server"))
26
+ #
27
+ # @param [Symbol] what the type of the object to take a screenshot of,
28
+ # possible values are _:foreground_, _:desktop_ and _:window_.
29
+ # @param [Hash] opts options are optional for specifying _:context_ to take a screenshot.
30
+ # It is possible to specify as many options as are needed for searching for the unique window.
31
+ # By default the first window with matching identifiers will be taken screenshot of.
32
+ # It is possible to use in addition to other options a 0-based _:index_ option to search for other windows if multiple
33
+ # windows match the specified criteria.
34
+ # @option opts [String, Regexp] :title Title of the window
35
+ # @option opts [String, Regexp] :text Visible text of the window
36
+ # @option opts [String, Fixnum] :hwnd Window handle in decimal format
37
+ # @option opts [String, Fixnum] :pid Window process ID (PID)
38
+ # @option opts [String, Fixnum] :index 0-based index to specify n-th window to take a screenshot of if
39
+ # all other criteria match
40
+ # @option opts [RAutomation::Window] :rautomation RAutomation::Window object to take a screenshot of. Useful for
41
+ # taking screenshots of the child windows
42
+ # @return [Image] the {Image} of the specified object
43
+ def of(what, opts = {})
44
+ valid_whats = [:foreground, :desktop, :window]
45
+ raise "It is not possible to take a screenshot of '#{what}', possible values are #{valid_whats.join(", ")}" unless valid_whats.include?(what)
46
+
47
+ self.send(what, opts)
48
+ end
49
+
50
+ alias_method :new, :of
51
+
52
+ private
53
+
54
+ def foreground(opts)
55
+ hwnd = BitmapMaker.foreground_window
56
+ BitmapMaker.capture_window(hwnd)
57
+ end
58
+
59
+ def desktop(opts)
60
+ hwnd = BitmapMaker.desktop_window
61
+ BitmapMaker.capture_screen(hwnd)
62
+ end
63
+
64
+ def window(opts)
65
+ win = opts[:rautomation] || RAutomation::Window.new(opts)
66
+ timeout = Time.now + 10
67
+ until win.active?
68
+ if Time.now >= timeout
69
+ Kernel.warn "[WARN] Failed to set window '#{win.locators.inspect}' into focus for taking the screenshot"
70
+ break
71
+ end
72
+ win.activate
73
+ end
74
+ BitmapMaker.capture_window(win.hwnd)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,5 +1,5 @@
1
- module Win32
2
- module Screenshot
3
- VERSION = "2.1.0"
4
- end
5
- end
1
+ module Win32
2
+ module Screenshot
3
+ VERSION = "4.0.0"
4
+ end
5
+ end
@@ -1,11 +1,12 @@
1
- require 'ffi'
2
- require 'mini_magick'
3
- require 'rautomation'
4
-
5
- require File.dirname(__FILE__) + '/screenshot/version'
6
- require File.dirname(__FILE__) + '/screenshot/take'
7
- require File.dirname(__FILE__) + '/screenshot/image'
8
- require File.dirname(__FILE__) + '/screenshot/bitmap_maker'
9
-
10
- # add bundled ImageMagick into path
11
- ENV["PATH"] = "#{File.dirname(__FILE__) + "/../../ext/ImageMagick-6.9.3-0-portable-Q16-x86"};#{ENV["PATH"]}"
1
+ require 'ffi'
2
+ require 'mini_magick'
3
+ require 'rautomation'
4
+
5
+ require File.dirname(__FILE__) + '/screenshot/version'
6
+ require File.dirname(__FILE__) + '/screenshot/take'
7
+ require File.dirname(__FILE__) + '/screenshot/image'
8
+ require File.dirname(__FILE__) + '/screenshot/bitmap_maker'
9
+ require File.dirname(__FILE__) + '/screenshot/desktop'
10
+
11
+ # add bundled ImageMagick into path
12
+ ENV["PATH"] = "#{File.dirname(__FILE__) + "/../../ext/ImageMagick-7.0.9-8-portable-Q16-x86"};#{ENV["PATH"]}"
data/spec/spec_helper.rb CHANGED
@@ -1,47 +1,47 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
- require 'win32/screenshot'
4
- require 'rspec'
5
- require 'fileutils'
6
-
7
- module SpecHelper
8
- HWND_TOPMOST = -1
9
- HWND_NOTOPMOST = -2
10
- SWP_NOSIZE = 1
11
- SWP_NOMOVE = 2
12
- SWP_SHOWWINDOW = 40
13
-
14
- extend FFI::Library
15
- ffi_lib 'user32'
16
- ffi_convention :stdcall
17
-
18
- # user32.dll
19
- attach_function :set_window_pos, :SetWindowPos,
20
- [:long, :long, :int, :int, :int, :int, :int], :bool
21
-
22
- def save_and_verify_image(img, file=nil)
23
- FileUtils.mkdir @temp_dir unless File.exists?(@temp_dir)
24
- file_name = File.join @temp_dir, "#{file}.bmp"
25
- img.write file_name
26
- img.bitmap[0..1].should == 'BM'
27
- end
28
-
29
- def resize title
30
- hwnd = RAutomation::Window.new(:title => title).hwnd
31
- set_window_pos(hwnd,
32
- HWND_TOPMOST,
33
- 0, 0, 150, 238,
34
- SWP_NOMOVE)
35
- set_window_pos(hwnd,
36
- HWND_NOTOPMOST,
37
- 0, 0, 0, 0,
38
- SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE)
39
- sleep 1
40
- end
41
- end
42
-
43
- RSpec.configure do |config|
44
- config.include(SpecHelper)
45
- config.before(:suite) {FileUtils.rm Dir.glob(File.join(File.dirname(__FILE__), "tmp/*"))}
46
- config.before(:all) {@temp_dir = File.join(File.dirname(__FILE__), 'tmp')}
47
- end
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'win32/screenshot'
4
+ require 'rspec'
5
+ require 'fileutils'
6
+
7
+ module SpecHelper
8
+ HWND_TOPMOST = -1
9
+ HWND_NOTOPMOST = -2
10
+ SWP_NOSIZE = 1
11
+ SWP_NOMOVE = 2
12
+ SWP_SHOWWINDOW = 40
13
+
14
+ extend FFI::Library
15
+ ffi_lib 'user32'
16
+ ffi_convention :stdcall
17
+
18
+ # user32.dll
19
+ attach_function :set_window_pos, :SetWindowPos,
20
+ [:long, :long, :int, :int, :int, :int, :int], :bool
21
+
22
+ def save_and_verify_image(img, file=nil)
23
+ FileUtils.mkdir @temp_dir unless File.exists?(@temp_dir)
24
+ file_name = File.join @temp_dir, "#{file}.bmp"
25
+ img.write file_name
26
+ expect(img.bitmap[0..1]).to eq('BM')
27
+ end
28
+
29
+ def resize title
30
+ hwnd = RAutomation::Window.new(:title => title).hwnd
31
+ set_window_pos(hwnd,
32
+ HWND_TOPMOST,
33
+ 0, 0, 150, 238,
34
+ SWP_NOMOVE)
35
+ set_window_pos(hwnd,
36
+ HWND_NOTOPMOST,
37
+ 0, 0, 0, 0,
38
+ SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE)
39
+ sleep 1
40
+ end
41
+ end
42
+
43
+ RSpec.configure do |config|
44
+ config.include(SpecHelper)
45
+ config.before(:suite) {FileUtils.rm Dir.glob(File.join(File.dirname(__FILE__), "tmp/*"))}
46
+ config.before(:all) {@temp_dir = File.join(File.dirname(__FILE__), 'tmp')}
47
+ end