win32-captureie 0.1.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.
Files changed (46) hide show
  1. data/History.txt +3 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +45 -0
  4. data/README.txt +49 -0
  5. data/Rakefile +4 -0
  6. data/TODO.txt +78 -0
  7. data/bin/prtie +62 -0
  8. data/config/hoe.rb +72 -0
  9. data/config/requirements.rb +17 -0
  10. data/lib/win32/capture_ie/base.rb +4 -0
  11. data/lib/win32/capture_ie/bitmap.rb +115 -0
  12. data/lib/win32/capture_ie/browser.rb +119 -0
  13. data/lib/win32/capture_ie/cli/base.rb +7 -0
  14. data/lib/win32/capture_ie/cli/prt_ie.rb +100 -0
  15. data/lib/win32/capture_ie/commands/base.rb +39 -0
  16. data/lib/win32/capture_ie/commands/prt_ie.rb +66 -0
  17. data/lib/win32/capture_ie/ffi/base.rb +22 -0
  18. data/lib/win32/capture_ie/ffi/gdi32.rb +165 -0
  19. data/lib/win32/capture_ie/ffi/struct.rb +246 -0
  20. data/lib/win32/capture_ie/ffi/user32.rb +101 -0
  21. data/lib/win32/capture_ie/ffi.rb +3 -0
  22. data/lib/win32/capture_ie/screen_captor.rb +131 -0
  23. data/lib/win32/capture_ie/version.rb +11 -0
  24. data/lib/win32/capture_ie/window.rb +47 -0
  25. data/lib/win32/capture_ie.rb +92 -0
  26. data/script/destroy +14 -0
  27. data/script/destroy.cmd +1 -0
  28. data/script/generate +14 -0
  29. data/script/generate.cmd +1 -0
  30. data/script/rdoc_filter.rb +75 -0
  31. data/script/txt2html +74 -0
  32. data/script/txt2html.cmd +1 -0
  33. data/setup.rb +1585 -0
  34. data/spec/spec.opts +1 -0
  35. data/spec/spec_helper.rb +7 -0
  36. data/spec/win32/capture_ie_spec.rb +11 -0
  37. data/tasks/deployment.rake +34 -0
  38. data/tasks/deployment2.rake +90 -0
  39. data/tasks/environment.rake +7 -0
  40. data/tasks/helper/rake.rb +58 -0
  41. data/tasks/helper/rake_sh_filter.rb +23 -0
  42. data/tasks/helper/util.rb +19 -0
  43. data/tasks/helper.rb +3 -0
  44. data/tasks/rspec.rake +21 -0
  45. data/tasks/website.rake +17 -0
  46. metadata +95 -0
@@ -0,0 +1,100 @@
1
+ require "optparse"
2
+
3
+ require "win32/capture_ie/cli/base"
4
+ require "win32/capture_ie/commands/prt_ie"
5
+
6
+
7
+ module Win32::CaptureIE::CLI
8
+ class PrtIE #:nodoc:
9
+
10
+ DEFAULT_ARGUMENTS = %W(--output-digest=MD5)
11
+
12
+ def self.start(args)
13
+ self.new.perform(args)
14
+ end
15
+
16
+
17
+ def arguable(arr_or_arguable)
18
+ case arr_or_arguable
19
+ when OptionParser::Arguable
20
+ arr_or_arguable
21
+ when Array
22
+ arr_or_arguable.dup.extend(OptionParser::Arguable)
23
+ else
24
+ raise ArgumentError, "Invalid option `#{arr_or_arguable.inspect}:#{arr_or_arguable.class}'" +
25
+ " (expected Array or OptionParser::Arguable)"
26
+ end
27
+ end
28
+
29
+ def usage_and_exit(exit_code, message, parser, out=$stdout)
30
+ m = message.sub(/\.?\z/, ".")
31
+ out.puts "Error: #{m}"
32
+ out.puts parser
33
+ exit(2)
34
+ end
35
+
36
+ def perform(args)
37
+ opt, parser = parse_options(arguable(args))
38
+ act = Win32::CaptureIE::Commands::PrtIE::Action.new
39
+ begin
40
+ act.perform(opt)
41
+ rescue ArgumentError => e
42
+ usage_and_exit(2, e.message, parser)
43
+ end
44
+ end
45
+
46
+ def option_parser(opt)
47
+ Proc.new do |parser|
48
+ parser.program_name = "prtie"
49
+ parser.version = Win32::CaptureIE::VERSION::STRING
50
+ parser.banner = "Usage: #{parser.program_name} [options] URL"
51
+ parser.separator ""
52
+ parser.on("-w", "--wait=SECONDS", Float, "wait SECONDS between retrievals") {|v|
53
+ opt.wait = v
54
+ }
55
+ parser.on("-d", "--output-directory=DIRECTORY", "save capture to DIRECTORY/...") {|v|
56
+ opt.outdir = v
57
+ }
58
+ parser.on("-o", "--output=FILE", "save capture to FILE") {|v|
59
+ opt.output = v
60
+ }
61
+ parser.on("-D", "--output-digest=ALGORITHM",
62
+ "use output filename as hex-encoded version",
63
+ "of a given URL",
64
+ "ALGORITHM: MD5, SHA1, SHA512 ...") {|v|
65
+ opt.output_digest = v
66
+ }
67
+
68
+ parser.separator ""
69
+ parser.on("-v", "--version", "print the version"){
70
+ puts parser.ver
71
+ exit 0
72
+ }
73
+ parser.on("-h", "--help", "print this message"){
74
+ puts parser
75
+ exit 0
76
+ }
77
+
78
+ parser.separator ""
79
+ parser.separator " Default options:"
80
+ parser.separator " #{DEFAULT_ARGUMENTS * ' '}"
81
+ parser
82
+ end
83
+ end
84
+
85
+ def parse_options(args)
86
+ opt = Win32::CaptureIE::Commands::PrtIE::Option.new
87
+ proc = option_parser(opt)
88
+ arguable(DEFAULT_ARGUMENTS).options(&proc).parse!
89
+ parser = args.options(&proc)
90
+ begin
91
+ parser.parse!
92
+ rescue OptionParser::ParseError => e
93
+ usage_and_exit(2, e.message, parser)
94
+ end
95
+ opt.url = ARGV.shift
96
+ [opt, parser]
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,39 @@
1
+ module Win32
2
+ module CaptureIE
3
+ module Commands #:nodoc:
4
+ end
5
+ end
6
+ end
7
+
8
+ module Win32::CaptureIE::Commands
9
+ class OptionBase #:nodoc:
10
+ def self.define_option_category(name)
11
+ class_eval %Q{
12
+ @@#{name} = Hash.new{|h,k| h[k] = []}
13
+ def self.#{name}(*names)
14
+ attr_accessor *names
15
+ @@#{name}[self].concat(names)
16
+ end
17
+
18
+ def self.#{name}s
19
+ @@#{name}[self]
20
+ end
21
+
22
+ def init_#{name}s
23
+ self.class.#{name}s.each do |e|
24
+ instance_variable_set("@\#{e}", nil)
25
+ end
26
+ end
27
+ }
28
+ end
29
+
30
+ define_option_category(:local_option)
31
+ define_option_category(:global_option)
32
+
33
+ def initialize
34
+ init_local_options
35
+ init_global_options
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,66 @@
1
+ require "fileutils"
2
+ require "digest/md5"
3
+
4
+ require "win32/capture_ie"
5
+ require "win32/capture_ie/commands/base"
6
+
7
+
8
+ module Win32::CaptureIE::Commands
9
+ module PrtIE #:nodoc:
10
+
11
+ class Option < OptionBase #:nodoc:
12
+ global_option :outdir, :wait, :output_digest
13
+ local_option :output, :url
14
+ attr_reader :ext
15
+
16
+ def initialize
17
+ @ext = "bmp"
18
+ end
19
+
20
+ def check_options
21
+ raise_arg_error(url, "missing url argument")
22
+ raise_arg_error(output || output_digest, "missing output or output-digest argument")
23
+ if output_digest
24
+ begin
25
+ Digest(output_digest)
26
+ rescue => e
27
+ raise ArgumentError, e.message
28
+ end
29
+ end
30
+ end
31
+
32
+ def raise_arg_error(ok, msg)
33
+ raise ArgumentError, msg unless ok
34
+ end
35
+
36
+ def digest_url
37
+ Digest(output_digest).hexdigest(url)
38
+ end
39
+
40
+ def fixup!
41
+ self.wait = 0 if wait.nil? or wait < 0
42
+ end
43
+
44
+ def outfile
45
+ base = output || "#{digest_url}.#{ext}"
46
+ r = File.expand_path(base, outdir)
47
+ dir = File.dirname(r)
48
+ ::FileUtils.mkdir_p(dir) unless File.exist?(dir)
49
+ r
50
+ end
51
+ end
52
+
53
+ class Action #:nodoc:
54
+ def perform(opt)
55
+ opt.fixup!
56
+ opt.check_options
57
+ Win32::CaptureIE.start do |ie|
58
+ ie.navigate(opt.url, true)
59
+ sleep(opt.wait) if opt.wait > 0
60
+ ie.capture_page(opt.outfile)
61
+ end
62
+ end
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ require "Win32API"
2
+
3
+ module Win32::CaptureIE
4
+ module FFI #:nodoc:
5
+ end
6
+
7
+ class Win32APIError < StandardError #:nodoc:
8
+ end
9
+ end
10
+
11
+ module Win32::CaptureIE::FFI
12
+ module Base #:nodoc:
13
+ def define_ffi_entry(const, args, ret, lib, entry=const.to_s)
14
+ api = ::Win32API.new(lib, entry, args, ret)
15
+ const_set(const, api)
16
+ define_method(const) do |*args|
17
+ api.call(*args)
18
+ end
19
+ module_function const
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,165 @@
1
+ require "win32/capture_ie/ffi/base"
2
+ require "win32/capture_ie/ffi/struct"
3
+
4
+ module Win32::CaptureIE::FFI
5
+ module GDI32 #:nodoc:
6
+ extend Win32::CaptureIE::FFI::Base
7
+ extend Win32::CaptureIE::FFI::CStruct
8
+
9
+ define_c_type(:uint8, :BYTE)
10
+ define_c_type(:uint16, :WORD)
11
+ define_c_type(:uint32, :DWORD)
12
+ define_c_type(:int32, :LONG)
13
+
14
+ define_c_struct(:RGBQUAD) {
15
+ BYTE :rgbBlue
16
+ BYTE :rgbGreen
17
+ BYTE :rgbRed
18
+ BYTE :rgbReserved
19
+ }
20
+
21
+ define_c_struct(:BITMAPINFOHEADER) {
22
+ DWORD :biSize
23
+ LONG :biWidth
24
+ LONG :biHeight
25
+ WORD :biPlanes
26
+ WORD :biBitCount
27
+ DWORD :biCompression
28
+ DWORD :biSizeImage
29
+ LONG :biXPelsPerMeter
30
+ LONG :biYPelsPerMeter
31
+ DWORD :biClrUsed
32
+ DWORD :biClrImportant
33
+ }
34
+
35
+ define_c_struct(:BITMAPINFO) {
36
+ BITMAPINFOHEADER :bmiHeader
37
+ RGBQUAD :bmiColors, :dim => 1
38
+ }
39
+
40
+ define_c_struct(:BITMAPFILEHEADER) {
41
+ WORD :bfType
42
+ DWORD :bfSize, :offset => 2
43
+ WORD :bfReserved1
44
+ WORD :bfReserved2
45
+ DWORD :bfOffBits, :offset => 10
46
+ }
47
+
48
+ DRIVERVERSION = 0
49
+ TECHNOLOGY = 2
50
+ HORZSIZE = 4
51
+ VERTSIZE = 6
52
+ HORZRES = 8
53
+ VERTRES = 10
54
+ BITSPIXEL = 12
55
+ PLANES = 14
56
+ NUMBRUSHES = 16
57
+ NUMPENS = 18
58
+ NUMMARKERS = 20
59
+ NUMFONTS = 22
60
+ NUMCOLORS = 24
61
+ PDEVICESIZE = 26
62
+ CURVECAPS = 28
63
+ LINECAPS = 30
64
+ POLYGONALCAPS = 32
65
+ TEXTCAPS = 34
66
+ CLIPCAPS = 36
67
+ RASTERCAPS = 38
68
+ ASPECTX = 40
69
+ ASPECTY = 42
70
+ ASPECTXY = 44
71
+
72
+ DIB_RGB_COLORS = 0
73
+ DIB_PAL_COLORS = 1
74
+
75
+ # Raster operations
76
+ SRCCOPY = 0x00CC0020
77
+ SRCPAINT = 0x00EE0086
78
+ SRCAND = 0x008800C6
79
+ SRCINVERT = 0x00660046
80
+ SRCERASE = 0x00440328
81
+ NOTSRCCOPY = 0x00330008
82
+ NOTSRCERASE = 0x001100A6
83
+ MERGECOPY = 0x00C000CA
84
+ MERGEPAINT = 0x00BB0226
85
+ PATCOPY = 0x00F00021
86
+ PATPAINT = 0x00FB0A09
87
+ PATINVERT = 0x005A0049
88
+ DSTINVERT = 0x00550009
89
+ BLACKNESS = 0x00000042
90
+ WHITENESS = 0x00FF0062
91
+
92
+ # constants for the biCompression field
93
+ BI_RGB = 0
94
+ BI_RLE8 = 1
95
+ BI_RLE4 = 2
96
+ BI_BITFIELDS = 3
97
+ BI_JPEG = 4
98
+ BI_PNG = 5
99
+
100
+ define_ffi_entry(:BitBlt, "LIIIILIIL", "B", "gdi32")
101
+ define_ffi_entry(:CreateCompatibleDC, "L", "L", "gdi32")
102
+ define_ffi_entry(:CreateCompatibleBitmap, "LII", "L", "gdi32")
103
+ define_ffi_entry(:DeleteDC, "L", "B", "gdi32")
104
+ define_ffi_entry(:DeleteObject, "L", "B", "gdi32")
105
+ define_ffi_entry(:GetDIBits, "LLIIPPI", "I", "gdi32")
106
+ define_ffi_entry(:SelectObject, "LL", "L", "gdi32")
107
+
108
+ module_function
109
+
110
+ def with_delete_dc(hdc)
111
+ begin
112
+ yield hdc
113
+ ensure
114
+ DeleteDC(hdc)
115
+ end
116
+ end
117
+
118
+ def with_delete_object(obj)
119
+ begin
120
+ yield obj
121
+ ensure
122
+ DeleteObject(obj)
123
+ end
124
+ end
125
+
126
+ end
127
+ end
128
+
129
+
130
+ __END__
131
+ from WinGDI.h
132
+
133
+ typedef struct tagBITMAPINFOHEADER{
134
+ DWORD biSize;
135
+ LONG biWidth;
136
+ LONG biHeight;
137
+ WORD biPlanes;
138
+ WORD biBitCount;
139
+ DWORD biCompression;
140
+ DWORD biSizeImage;
141
+ LONG biXPelsPerMeter;
142
+ LONG biYPelsPerMeter;
143
+ DWORD biClrUsed;
144
+ DWORD biClrImportant;
145
+ } BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
146
+
147
+ typedef struct tagRGBQUAD {
148
+ BYTE rgbBlue;
149
+ BYTE rgbGreen;
150
+ BYTE rgbRed;
151
+ BYTE rgbReserved;
152
+ } RGBQUAD;
153
+
154
+ typedef struct tagBITMAPINFO {
155
+ BITMAPINFOHEADER bmiHeader;
156
+ RGBQUAD bmiColors[1];
157
+ } BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;
158
+
159
+ typedef struct tagBITMAPFILEHEADER {
160
+ WORD bfType;
161
+ DWORD bfSize;
162
+ WORD bfReserved1;
163
+ WORD bfReserved2;
164
+ DWORD bfOffBits;
165
+ } BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
@@ -0,0 +1,246 @@
1
+ require "win32/capture_ie/ffi/base"
2
+
3
+ module Win32::CaptureIE::FFI
4
+
5
+ class Type #:nodoc:
6
+ attr_reader :name
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+ end
11
+
12
+ class PrimitiveType < Type #:nodoc:
13
+ BASE_TYPES = {
14
+ :int8 => ["c", 1],
15
+ :uint8 => ["C", 1],
16
+ :int16 => ["s", 2],
17
+ :uint16 => ["S", 2],
18
+ :int32 => ["l", 4],
19
+ :uint32 => ["L", 4],
20
+ :float => ["f", 4],
21
+ :double => ["d", 8],
22
+ :string => ["p", 4],
23
+ :pointer => ["P", 4],
24
+ }
25
+
26
+ attr_reader :size, :format
27
+ def initialize(name, size=guess_size(name), format=guess_format(name))
28
+ super(name)
29
+ @size = size
30
+ @format = format
31
+ end
32
+
33
+ def pack(value)
34
+ [value].pack(format)
35
+ end
36
+
37
+ def guess_format(name)
38
+ BASE_TYPES[name][0]
39
+ end
40
+ private :guess_format
41
+
42
+ def guess_size(name)
43
+ BASE_TYPES[name][1]
44
+ end
45
+ private :guess_size
46
+ end
47
+
48
+ class StructureType < Type #:nodoc:
49
+ PACKING_ALIGN = 4
50
+
51
+ attr_reader :name, :size, :ruby_class, :fields
52
+ def initialize(name, size=nil)
53
+ super(name)
54
+ @ruby_class = nil
55
+ @fields = []
56
+ @size = size
57
+ end
58
+
59
+ def calc_offsets
60
+ offset = 0
61
+ @fields.each do |f|
62
+ align = [f.type.size, PACKING_ALIGN].min
63
+ offset = (offset.to_f / align).ceil * align
64
+ size = f.size
65
+ if f.offset.nil?
66
+ f.offset = offset
67
+ offset += size
68
+ else
69
+ offset = f.offset
70
+ offset += size
71
+ end
72
+ end
73
+ end
74
+
75
+ def calc_size
76
+ return @size if @size
77
+ if @fields.empty?
78
+ @size = 0
79
+ return
80
+ end
81
+ align = [@fields.first.type.size, PACKING_ALIGN].min
82
+ offset = @fields.last.offset + @fields.last.size
83
+ @size = (offset.to_f / align).ceil * align
84
+ end
85
+
86
+ def pack(value)
87
+ packed = @fields.zip(value.values).map{|f,v| [f.offset, f.pack(v)] }
88
+ r = ""
89
+ packed.each do |offset,c|
90
+ align!(r, offset)
91
+ r << c
92
+ end
93
+ align!(r, size)
94
+ r
95
+ end
96
+
97
+ def align!(r, offset)
98
+ if offset < r.length
99
+ raise "buffer over flow: expected offset #{offset}, but was #{r.length}"
100
+ end
101
+ if r.length < offset
102
+ r << "\0" * (offset - r.length)
103
+ end
104
+ end
105
+
106
+ def define!(ns)
107
+ calc_offsets
108
+ calc_size
109
+ define_ruby_class(ns)
110
+ end
111
+
112
+ def define_ruby_class(ns)
113
+ types = self
114
+ name = @name
115
+ clazz = Class.new
116
+ clazz.instance_eval {
117
+ types.fields.each {|f|
118
+ attr_accessor f.name
119
+ }
120
+ define_method(:initialize) {|*args|
121
+ types.fields.zip(args).each {|f,arg|
122
+ instance_variable_set(f.var, arg || f.init)
123
+ }
124
+ }
125
+ define_method(:pack) {
126
+ types.pack(self)
127
+ }
128
+ }
129
+ clazz.class_eval %Q{
130
+ def names
131
+ [#{types.fields.map{|f| ":#{f.name}"} * ','}]
132
+ end
133
+ def values
134
+ [#{types.fields.map{|f| f.var} * ','}]
135
+ end
136
+ }
137
+ (class <<clazz; self; end).instance_eval {
138
+ define_method(:packed_size) {
139
+ CStruct.sizeof(name)
140
+ }
141
+ define_method(:c_type) {
142
+ CStruct.c_type(name)
143
+ }
144
+ }
145
+ @ruby_class = clazz
146
+ ns.const_set(@name, clazz)
147
+ end
148
+ end
149
+
150
+ class Field #:nodoc:
151
+ attr_reader :type, :name, :count, :var
152
+ attr_accessor :offset
153
+ def initialize(type, name, opts=nil)
154
+ @type = type
155
+ @name = name
156
+ @var = "@#{name}".to_sym
157
+ @opts = opts || {}
158
+ @offset = @opts[:offset] || nil
159
+ @count = @opts[:dim] || 1
160
+ end
161
+
162
+ def array?
163
+ count > 0
164
+ end
165
+
166
+ def init
167
+ if array?
168
+ init_one
169
+ else
170
+ (0...count).map{ init_one }
171
+ end
172
+ end
173
+
174
+ def size
175
+ @type.size * count
176
+ end
177
+
178
+ def pack(value)
179
+ if count == 1
180
+ @type.pack(value)
181
+ else
182
+ raise "hoge" unless Array === value
183
+ raise "foo" unless value.length == count
184
+ value.map{|e| @type.pack(e) }.join
185
+ end
186
+ end
187
+
188
+ def init_one
189
+ if PrimitiveType === @type
190
+ 0
191
+ else
192
+ @type.ruby_class.new
193
+ end
194
+ end
195
+ private :init_one
196
+ end
197
+
198
+ module CStruct #:nodoc:
199
+ TYPES = PrimitiveType::BASE_TYPES.inject({}){|h,t|
200
+ h[t[0]] = PrimitiveType.new(t[0])
201
+ h
202
+ }
203
+
204
+ def define_c_struct(name, size=nil, &block)
205
+ define_c_struct_under(self, name, size, &block)
206
+ end
207
+
208
+ module_function
209
+
210
+ def define_c_struct_under(ns, name, size=nil, &block)
211
+ dsl = DSL.new(name, size)
212
+ dsl.instance_eval(&block)
213
+ dsl.struct.define!(ns)
214
+ TYPES[name] = dsl.struct
215
+ dsl.struct
216
+ end
217
+
218
+ def sizeof(type)
219
+ c_type(type).size
220
+ end
221
+
222
+ def c_type(type)
223
+ TYPES[type]
224
+ end
225
+
226
+ def define_c_type(type, decl)
227
+ TYPES[decl] = TYPES[type]
228
+ end
229
+
230
+ class DSL #:nodoc:
231
+ attr_reader :struct
232
+
233
+ def initialize(name, size)
234
+ @struct = StructureType.new(name, size)
235
+ end
236
+
237
+ def method_missing(name, *args)
238
+ type = CStruct.c_type(name)
239
+ raise "unknown type: #{name}" unless type
240
+ name = args.shift
241
+ @struct.fields << Field.new(type, name, *args)
242
+ end
243
+ end
244
+ end
245
+
246
+ end