win32screenshot 2.1.0 → 4.0.0

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,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