win32-clipboard 0.5.2 → 0.6.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,29 +1,29 @@
1
- ##########################################################################
2
- # clipboard_test.rb (win32-clipboard)
3
- #
4
- # Generic test script for those without Test::Unit installed, or for
5
- # general futzing. You can run this example via the 'rake example' task.
6
- ##########################################################################
7
- require "win32/clipboard"
8
- require "pp"
9
- include Win32
10
-
11
- puts "VERSION: " + Clipboard::VERSION
12
-
13
- pp Clipboard.formats
14
- pp Clipboard.data(Clipboard::UNICODETEXT)
15
- pp Clipboard.format_available?(49161)
16
- pp Clipboard.format_name(999999999)
17
- pp Clipboard.format_available?(9999999)
18
-
19
- puts "Data was: [" + Clipboard.data + "]"
20
-
21
- Clipboard.set_data("foobar")
22
-
23
- puts "Data is now: [" + Clipboard.data + "]"
24
-
25
- puts "Number of available formats: " + Clipboard.num_formats.to_s
26
-
27
- Clipboard.empty
28
-
1
+ ##########################################################################
2
+ # clipboard_test.rb (win32-clipboard)
3
+ #
4
+ # Generic test script for those without Test::Unit installed, or for
5
+ # general futzing. You can run this example via the 'rake example' task.
6
+ ##########################################################################
7
+ require "win32/clipboard"
8
+ require "pp"
9
+ include Win32
10
+
11
+ puts "VERSION: " + Clipboard::VERSION
12
+
13
+ pp Clipboard.formats
14
+ pp Clipboard.data(Clipboard::UNICODETEXT)
15
+ pp Clipboard.format_available?(49161)
16
+ pp Clipboard.format_name(999999999)
17
+ pp Clipboard.format_available?(9999999)
18
+
19
+ puts "Data was: [" + Clipboard.data + "]"
20
+
21
+ Clipboard.set_data("foobar")
22
+
23
+ puts "Data is now: [" + Clipboard.data + "]"
24
+
25
+ puts "Number of available formats: " + Clipboard.num_formats.to_s
26
+
27
+ Clipboard.empty
28
+
29
29
  puts "Clipboard emptied"
@@ -1,412 +1,414 @@
1
- require 'windows/clipboard'
2
- require 'windows/memory'
3
- require 'windows/error'
4
- require 'windows/shell'
5
- require 'windows/library'
6
- require 'windows/window'
7
- require 'windows/msvcrt/buffer'
8
- require 'windows/gdi/metafile'
9
- require 'windows/window/message'
10
- require 'windows/window/classes'
11
-
12
- # The Win32 module serves as a namespace only.
13
- module Win32
14
- # The Clipboard class encapsulates functions that relate to the MS Windows
15
- # clipboard.
16
- class Clipboard
17
- # The Clipboard::Error class is raised if any of the Win32::Clipboard
18
- # methods should fail.
19
- class Error < StandardError; end
20
-
21
- include Windows::Clipboard
22
- include Windows::Memory
23
- include Windows::Error
24
- include Windows::Window::Classes
25
- include Windows::Window::Message
26
-
27
- extend Windows::Clipboard
28
- extend Windows::Memory
29
- extend Windows::Error
30
- extend Windows::Shell
31
- extend Windows::Library
32
- extend Windows::Window
33
- extend Windows::MSVCRT::Buffer
34
- extend Windows::GDI::MetaFile
35
- extend Windows::Window::Message
36
- extend Windows::Window::Classes
37
-
38
- # The version of this library
39
- VERSION = '0.5.2'
40
-
41
- # Clipboard formats
42
-
43
- # Text
44
- TEXT = CF_TEXT
45
- OEMTEXT = CF_OEMTEXT
46
- UNICODETEXT = CF_UNICODETEXT
47
-
48
- # Images
49
- DIB = CF_DIB
50
- BITMAP = CF_BITMAP
51
-
52
- # Metafiles
53
- ENHMETAFILE = CF_ENHMETAFILE
54
-
55
- # Files
56
- HDROP = CF_HDROP
57
-
58
- # Sets the clipboard contents to the data that you specify. You may
59
- # optionally specify a clipboard format. The default is Clipboard::TEXT.
60
- #
61
- # Example:
62
- #
63
- # # Put the string 'hello' on the clipboard
64
- # Win32::Clipboard.set_data('hello')
65
- #
66
- def self.set_data(clip_data, format = TEXT)
67
- self.open
68
- EmptyClipboard()
69
-
70
- # NULL terminate text
71
- case format
72
- when TEXT, OEMTEXT, UNICODETEXT
73
- clip_data << "\0"
74
- end
75
-
76
- # Global Allocate a movable piece of memory.
77
- hmem = GlobalAlloc(GHND, clip_data.length + 4)
78
- mem = GlobalLock(hmem)
79
- memcpy(mem, clip_data, clip_data.length)
80
-
81
- # Set the new data
82
- begin
83
- if SetClipboardData(format, hmem) == 0
84
- raise Error, "SetClipboardData() failed: " + get_last_error
85
- end
86
- ensure
87
- GlobalFree(hmem)
88
- self.close
89
- end
90
-
91
- self
92
- end
93
-
94
- # Returns the data currently in the clipboard. If +format+ is
95
- # specified, it will attempt to retrieve the data in that format. The
96
- # default is Clipboard::TEXT.
97
- #
98
- # If there is no data in the clipboard, or data is available but the
99
- # format doesn't match the data, then an empty string is returned.
100
- #
101
- # Examples:
102
- #
103
- # # Get some plain text
104
- # Win32::Clipboard.data # => e.g. 'hello'
105
- #
106
- # # Get a list of files copied from the Windows Explorer window
107
- # Win32::Clipboard.data(Clipboard::HDROP) # => ['foo.rb', 'bar.rb']
108
- #
109
- # # Get a bitmap and write it to another file
110
- # File.open('bitmap_copy', 'wb'){ |fh|
111
- # fh.write Win32::Clipboard.data(Clipboard::DIB)
112
- # }
113
- #
114
- def self.data(format = TEXT)
115
- begin
116
- self.open
117
- if IsClipboardFormatAvailable(format)
118
- handle = GetClipboardData(format)
119
- case format
120
- when TEXT, OEMTEXT, UNICODETEXT
121
- clip_data = 0.chr * GlobalSize(handle)
122
- memcpy(clip_data, handle, clip_data.size)
123
- clip_data = clip_data[ /^[^\0]*/ ]
124
- when HDROP
125
- clip_data = get_file_list(handle)
126
- when ENHMETAFILE
127
- clip_data = get_metafile_data(handle)
128
- when DIB, BITMAP
129
- clip_data = get_image_data(handle)
130
- else
131
- raise Error, 'format not supported'
132
- end
133
- else
134
- clip_data = ''
135
- end
136
- ensure
137
- self.close
138
- end
139
-
140
- clip_data
141
- end
142
-
143
- # Empties the contents of the clipboard.
144
- #
145
- def self.empty
146
- begin
147
- self.open
148
- EmptyClipboard()
149
- ensure
150
- self.close
151
- end
152
-
153
- self
154
- end
155
-
156
- # Singleton aliases
157
- #
158
- class << self
159
- alias :get_data :data
160
- alias :clear :empty
161
- end
162
-
163
- # Returns the number of different data formats currently on the
164
- # clipboard.
165
- #
166
- def self.num_formats
167
- count = 0
168
-
169
- begin
170
- self.open
171
- count = CountClipboardFormats()
172
- ensure
173
- self.close
174
- end
175
-
176
- count
177
- end
178
-
179
- # Returns whether or not +format+ (an int) is currently available.
180
- #
181
- def self.format_available?(format)
182
- IsClipboardFormatAvailable(format)
183
- end
184
-
185
- # Returns the corresponding name for the given +format_num+, or nil
186
- # if it does not exist. You cannot specify any of the predefined
187
- # clipboard formats (or nil is returned), only registered formats.
188
- #
189
- def self.format_name(format_num)
190
- val = nil
191
- buf = 0.chr * 80
192
-
193
- begin
194
- self.open
195
- if GetClipboardFormatName(format_num, buf, buf.length) != 0
196
- val = buf
197
- end
198
- ensure
199
- self.close
200
- end
201
-
202
- val.split(0.chr).first rescue nil
203
- end
204
-
205
- # Returns a hash of all the current formats, with the format number as
206
- # the key and the format name as the value for that key.
207
- #
208
- # Example:
209
- #
210
- # Win32::Clipboard.formats # => {1 => nil, 49335 => "HTML Format"}
211
- #
212
- def self.formats
213
- formats = {}
214
- format = 0
215
-
216
- begin
217
- self.open
218
- while 0 != (format = EnumClipboardFormats(format))
219
- buf = 0.chr * 80
220
- GetClipboardFormatName(format, buf, buf.length)
221
- formats[format] = buf.split(0.chr).first
222
- end
223
- ensure
224
- self.close
225
- end
226
-
227
- formats
228
- end
229
-
230
- # Registers the given +format+ (a String) as a clipboard format, which
231
- # can then be used as a valid clipboard format. Returns the integer
232
- # value of the registered format.
233
- #
234
- # If a registered format with the specified name already exists, a new
235
- # format is not registered and the return value identifies the existing
236
- # format. This enables more than one application to copy and paste data
237
- # using the same registered clipboard format. Note that the format name
238
- # comparison is case-insensitive.
239
- #
240
- # Registered clipboard formats are identified by values in the range 0xC000
241
- # through 0xFFFF.
242
- #
243
- def self.register_format(format)
244
- raise TypeError unless format.is_a?(String)
245
-
246
- format_value = RegisterClipboardFormat(format)
247
-
248
- if format_value == 0
249
- error = "RegisterClipboardFormat() call failed: " + get_last_error
250
- raise Error, error
251
- end
252
-
253
- format_value
254
- end
255
-
256
- # Sets up a notification loop that will call the provided block whenever
257
- # there's a change to the clipboard.
258
- #
259
- # Example:
260
- #
261
- # Win32::Clipboard.notify_change{
262
- # puts "There's been a change in the clipboard contents!"
263
- # }
264
- #--
265
- # We skip the first notification because the very act of attaching the
266
- # new window causes it to trigger once.
267
- #
268
- def self.notify_change(&block)
269
- name = 'ruby-clipboard-' + Time.now.to_s
270
- handle = CreateWindow('static', name, 0, 0, 0, 0, 0, 0, 0, 0, 0)
271
-
272
- @first_notify = true
273
-
274
- wnd_proc = Win32::API::Callback.new('LLLL', 'L') do |hwnd, umsg, wparam, lparam|
275
- case umsg
276
- when WM_DRAWCLIPBOARD
277
- yield unless @first_notify
278
- next_viewer = GetWindowLongPtr(hwnd, GWL_USERDATA)
279
- if next_viewer != 0
280
- PostMessage(next_viewer, umsg, wparam, lparam)
281
- end
282
- rv = 0
283
- when WM_CHANGECBCHAIN
284
- yield unless @first_notify
285
- next_viewer = lparam if next_viewer == wparam
286
- if next_viewer != 0
287
- PostMessage(next_viewer, umsg, wparam, lparam)
288
- end
289
- rv = 0
290
- else
291
- rv = DefWindowProc(hwnd, umsg, wparam, lparam)
292
- end
293
-
294
- @first_notify = false
295
-
296
- rv
297
- end
298
-
299
- old_wnd_proc = SetWindowLongPtr(handle, GWL_WNDPROC, wnd_proc.address)
300
- next_viewer = SetClipboardViewer(handle)
301
-
302
- SetWindowLongPtr(handle, GWL_USERDATA, next_viewer)
303
-
304
- msg = 0.chr * 100
305
-
306
- while true
307
- while PeekMessage(msg, handle, 0, 0, 1)
308
- TranslateMessage(msg)
309
- DispatchMessage(msg)
310
- end
311
- sleep 0.1
312
- end
313
- end
314
-
315
- private
316
-
317
- # Opens the clipboard and prevents other applications from modifying
318
- # the clipboard content until it is closed.
319
- #
320
- def self.open
321
- if 0 == OpenClipboard(nil)
322
- raise Error, "OpenClipboard() failed: " + get_last_error
323
- end
324
- end
325
-
326
- # Close the clipboard
327
- #
328
- def self.close
329
- CloseClipboard()
330
- end
331
-
332
- # Get data for enhanced metadata files
333
- #
334
- def self.get_metafile_data(handle)
335
- buf_size = GetEnhMetaFileBits(handle, 0, nil)
336
- buf = 0.chr * buf_size
337
- GetEnhMetaFileBits(handle, buf_size, buf)
338
- buf
339
- end
340
-
341
- # Get data for bitmap files
342
- #
343
- def self.get_image_data(handle)
344
- buf = nil
345
- bmi = 0.chr * 44 # BITMAPINFO
346
-
347
- begin
348
- address = GlobalLock(handle)
349
- buf_size = GlobalSize(handle)
350
-
351
- memcpy(bmi, address, bmi.length)
352
-
353
- size = bmi[0,4].unpack('L').first # biSize
354
- bit_count = bmi[14,2].unpack('S').first # biBitCount
355
- compression = bmi[16,4].unpack('L').first # biCompression
356
- size_image = bmi[20,4].unpack('L').first # biSizeImage
357
- clr_used = bmi[32,4].unpack('L').first # biClrUsed
358
-
359
- size_image = buf_size + 16 if size_image == 0
360
-
361
- # Calculate the header size
362
- case bit_count
363
- when 1
364
- table_size = 2
365
- when 4
366
- table_size = 16
367
- when 8
368
- table_size = 256
369
- when 16, 32
370
- if compression == 0
371
- table_size = clr_used
372
- elsif compression == 3
373
- table_size = 3
374
- else
375
- raise Error, "invalid bit/compression combination"
376
- end
377
- when 24
378
- table_size = clr_used
379
- else
380
- raise Error, "invalid bit count"
381
- end # case
382
-
383
- offset = 0x36 + (table_size * 4)
384
- buf = 0.chr * buf_size
385
-
386
- memcpy(buf, address, buf.size)
387
-
388
- buf = "\x42\x4D" + [size_image].pack('L') + 0.chr * 4 + [offset].pack('L') + buf
389
- ensure
390
- GlobalUnlock(handle)
391
- end
392
-
393
- buf
394
- end
395
-
396
- # Get and return an array of file names that have been copied.
397
- #
398
- def self.get_file_list(handle)
399
- array = []
400
- count = DragQueryFile(handle, 0xFFFFFFFF, nil, 0)
401
-
402
- 0.upto(count - 1){ |i|
403
- length = DragQueryFile(handle, i, nil, 0) + 1
404
- buf = 0.chr * length
405
- DragQueryFile(handle, i, buf, buf.length)
406
- array << buf.strip
407
- }
408
-
409
- array
410
- end
411
- end
412
- end
1
+ require File.join(File.dirname(__FILE__), 'windows', 'constants')
2
+ require File.join(File.dirname(__FILE__), 'windows', 'structs')
3
+ require File.join(File.dirname(__FILE__), 'windows', 'functions')
4
+
5
+ # The Win32 module serves as a namespace only.
6
+ module Win32
7
+
8
+ # The Clipboard class encapsulates functions that relate to the MS Windows clipboard.
9
+ class Clipboard
10
+
11
+ include Windows::Constants
12
+ include Windows::Structs
13
+ include Windows::Functions
14
+
15
+ extend Windows::Functions
16
+ extend Windows::Structs
17
+
18
+ # The version of this library
19
+ VERSION = '0.6.0'
20
+
21
+ # Clipboard formats
22
+
23
+ # Text
24
+
25
+ TEXT = 1
26
+ OEMTEXT = 7
27
+ UNICODETEXT = 13
28
+
29
+ # Images
30
+
31
+ DIB = 8
32
+ BITMAP = 2
33
+
34
+ # Metafiles
35
+
36
+ ENHMETAFILE = 14
37
+
38
+ # Files
39
+
40
+ HDROP = 15
41
+
42
+ # Empties the contents of the clipboard.
43
+ #
44
+ def self.empty
45
+ begin
46
+ open
47
+ unless EmptyClipboard()
48
+ raise SystemCallError.new('EmptyClipboard', FFI.errno)
49
+ end
50
+ ensure
51
+ close
52
+ end
53
+ end
54
+
55
+ class << self
56
+ alias clear empty
57
+ end
58
+
59
+ # Returns the number of different data formats currently on the clipboard.
60
+ #
61
+ def self.num_formats
62
+ CountClipboardFormats() || 0
63
+ end
64
+
65
+ # Returns whether or not +format+ (an integer) is currently available.
66
+ #
67
+ def self.format_available?(format)
68
+ IsClipboardFormatAvailable(format)
69
+ end
70
+
71
+ # Returns the data currently in the clipboard. If +format+ is
72
+ # specified, it will attempt to retrieve the data in that format. The
73
+ # default is Clipboard::TEXT.
74
+ #
75
+ # If there is no data in the clipboard, or data is available but the
76
+ # format doesn't match the data, then an empty string is returned.
77
+ #
78
+ # Examples:
79
+ #
80
+ # # Get some plain text
81
+ # Win32::Clipboard.data # => e.g. 'hello'
82
+ #
83
+ # # Get a list of files copied from the Windows Explorer window
84
+ # Win32::Clipboard.data(Clipboard::HDROP) # => ['foo.rb', 'bar.rb']
85
+ #
86
+ # # Get a bitmap and write it to another file
87
+ # File.open('bitmap_copy', 'wb'){ |fh|
88
+ # fh.write Win32::Clipboard.data(Clipboard::DIB)
89
+ # }
90
+ #
91
+ def self.data(format = TEXT)
92
+ begin
93
+ open
94
+
95
+ if IsClipboardFormatAvailable(format)
96
+ handle = GetClipboardData(format)
97
+
98
+ case format
99
+ when UNICODETEXT
100
+ size = GlobalSize(handle)
101
+ ptr = GlobalLock(handle)
102
+ clip_data = ptr.read_bytes(size).force_encoding('UTF-16LE').encode('UTF-8').strip
103
+ when TEXT, OEMTEXT
104
+ size = GlobalSize(handle)
105
+ ptr = GlobalLock(handle)
106
+
107
+ clip_data = ptr.read_bytes(size).strip
108
+
109
+ unless clip_data.ascii_only?
110
+ clip_data.force_encoding('BINARY')
111
+ end
112
+ when HDROP
113
+ clip_data = get_file_list(handle)
114
+ when ENHMETAFILE
115
+ clip_data = get_metafile_data(handle)
116
+ when DIB, BITMAP
117
+ clip_data = get_image_data(handle)
118
+ else
119
+ raise "format '#{format}' not supported"
120
+ end
121
+ else
122
+ clip_data = ''
123
+ end
124
+ ensure
125
+ close
126
+ end
127
+
128
+ clip_data
129
+ end
130
+
131
+ class << self
132
+ alias get_data data
133
+ end
134
+
135
+ # Sets the clipboard contents to the data that you specify. You may
136
+ # optionally specify a clipboard format. The default is Clipboard::TEXT.
137
+ #
138
+ # Example:
139
+ #
140
+ # # Put the string 'hello' on the clipboard
141
+ # Win32::Clipboard.set_data('hello')
142
+ #
143
+ # # Put the image test.bmp on the clipboard
144
+ # f = File.open("test.bmp", "rb")
145
+ # str = f.read
146
+ # Clipboard.set_data(str, Win32::Clipboard::DIB)
147
+ #
148
+ def self.set_data(clip_data, format = TEXT)
149
+ begin
150
+ clear
151
+ open
152
+
153
+ # NULL terminate text or strip header of bitmap file
154
+ case format
155
+ when UNICODETEXT
156
+ clip_data.encode!('UTF-16LE')
157
+ clip_data << "\0".encode('UTF-16LE')
158
+ buf = clip_data
159
+ extra = 4
160
+ when TEXT, OEMTEXT
161
+ clip_data << "\0"
162
+ buf = clip_data
163
+ extra = 4
164
+ when BITMAP, DIB
165
+ buf = clip_data[14..clip_data.length] # sizeof(BITMAPFILEHEADER) = 14
166
+ extra = 0
167
+ end
168
+
169
+ # Global Allocate a movable piece of memory.
170
+ hmem = GlobalAlloc(GHND, buf.bytesize + extra)
171
+ mptr = GlobalLock(hmem)
172
+
173
+ mptr.write_bytes(buf, 0, buf.bytesize)
174
+
175
+ # Set the new data
176
+ if SetClipboardData(format, hmem) == 0
177
+ raise SystemCallError.new('SetClipboardData', FFI.errno)
178
+ end
179
+ ensure
180
+ GlobalFree(hmem)
181
+ close
182
+ end
183
+ end
184
+
185
+ # Returns a hash of all the current formats, with the format number as
186
+ # the key and the format name as the value for that key.
187
+ #
188
+ # Example:
189
+ #
190
+ # Win32::Clipboard.formats # => {1 => nil, 49335 => "HTML Format"}
191
+ #
192
+ def self.formats
193
+ formats = {}
194
+ format = 0
195
+
196
+ begin
197
+ self.open
198
+ while 0 != (format = EnumClipboardFormats(format))
199
+ buf = FFI::MemoryPointer.new(:char, 80)
200
+ GetClipboardFormatName(format, buf, buf.size)
201
+ string = buf.read_string
202
+ formats[format] = string.empty? ? nil : string
203
+ end
204
+ ensure
205
+ self.close
206
+ end
207
+
208
+ formats
209
+ end
210
+
211
+ # Returns the corresponding name for the given +format_num+, or nil
212
+ # if it does not exist. You cannot specify any of the predefined
213
+ # clipboard formats (or nil is returned), only registered formats.
214
+ #
215
+ def self.format_name(format_num)
216
+ val = nil
217
+ buf = FFI::MemoryPointer.new(:char, 80)
218
+
219
+ begin
220
+ open
221
+
222
+ if GetClipboardFormatName(format_num, buf, buf.size) != 0
223
+ val = buf.read_string
224
+ end
225
+ ensure
226
+ close
227
+ end
228
+
229
+ val
230
+ end
231
+
232
+ # Registers the given +format+ (a String) as a clipboard format, which
233
+ # can then be used as a valid clipboard format. Returns the integer
234
+ # value of the registered format.
235
+ #
236
+ # If a registered format with the specified name already exists, a new
237
+ # format is not registered and the return value identifies the existing
238
+ # format. This enables more than one application to copy and paste data
239
+ # using the same registered clipboard format. Note that the format name
240
+ # comparison is case-insensitive.
241
+ #
242
+ # Registered clipboard formats are identified by values in the range 0xC000
243
+ # through 0xFFFF.
244
+ #
245
+ def self.register_format(format)
246
+ format_value = RegisterClipboardFormat(format)
247
+
248
+ if format_value == 0
249
+ raise SystemCallError.new('RegisterClipboardFormat', FFI.errno)
250
+ end
251
+
252
+ format_value
253
+ end
254
+
255
+ # Sets up a notification loop that will call the provided block whenever
256
+ # there's a change to the clipboard.
257
+ #
258
+ # Example:
259
+ #
260
+ # Win32::Clipboard.notify_change{
261
+ # puts "There's been a change in the clipboard contents!"
262
+ # }
263
+ #--
264
+ # We skip the first notification because the very act of attaching the
265
+ # new window causes it to trigger once.
266
+ #
267
+ def self.notify_change(&block)
268
+ name = 'ruby-clipboard-' + Time.now.to_s
269
+ handle = CreateWindowEx(0, 'static', name, 0, 0, 0, 0, 0, 0, 0, 0, nil)
270
+
271
+ @first_notify = true
272
+
273
+ wnd_proc = FFI::Function.new(:uintptr_t, [:uintptr_t, :uint, :uintptr_t, :uintptr_t]) do |hwnd, umsg, wparam, lparam|
274
+ case umsg
275
+ when WM_DRAWCLIPBOARD
276
+ yield unless @first_notify
277
+ next_viewer = GetWindowLongPtr(hwnd, GWL_USERDATA)
278
+ if next_viewer != 0
279
+ PostMessage(next_viewer, umsg, wparam, lparam)
280
+ end
281
+ rv = 0
282
+ when WM_CHANGECBCHAIN
283
+ yield unless @first_notify
284
+ next_viewer = lparam if next_viewer == wparam
285
+ if next_viewer != 0
286
+ PostMessage(next_viewer, umsg, wparam, lparam)
287
+ end
288
+ rv = 0
289
+ else
290
+ rv = DefWindowProc(hwnd, umsg, wparam, lparam)
291
+ end
292
+
293
+ @first_notify = false
294
+
295
+ rv
296
+ end
297
+
298
+ if SetWindowLongPtr(handle, GWL_WNDPROC, wnd_proc.address) == 0
299
+ raise SystemCallError.new('SetWindowLongPtr', FFI.errno)
300
+ end
301
+
302
+ next_viewer = SetClipboardViewer(handle)
303
+
304
+ if next_viewer.nil?
305
+ raise SystemCallError.new('SetClipboardViewer', FFI.errno)
306
+ end
307
+
308
+ SetWindowLongPtr(handle, GWL_USERDATA, next_viewer)
309
+
310
+ msg = FFI::MemoryPointer.new(:char, 100)
311
+
312
+ while true
313
+ while PeekMessage(msg, handle, 0, 0, 1)
314
+ TranslateMessage(msg)
315
+ DispatchMessage(msg)
316
+ end
317
+ sleep 0.1
318
+ end
319
+ end
320
+
321
+ private
322
+
323
+ # Opens the clipboard and prevents other applications from modifying
324
+ # the clipboard content until it is closed.
325
+ #
326
+ def self.open
327
+ unless OpenClipboard(0)
328
+ raise SystemCallError.new('OpenClipboard', FFI.errno)
329
+ end
330
+ end
331
+
332
+ # Close the clipboard
333
+ #
334
+ def self.close
335
+ unless CloseClipboard()
336
+ raise SystemCallError.new('CloseClipboard', FFI.errno)
337
+ end
338
+ end
339
+
340
+ # Get data for enhanced metadata files
341
+ #
342
+ def self.get_metafile_data(handle)
343
+ buf_size = GetEnhMetaFileBits(handle, 0, nil)
344
+ buf = FFI::MemoryPointer.new(:char, buf_size)
345
+ GetEnhMetaFileBits(handle, buf.size, buf)
346
+ buf.read_string
347
+ end
348
+
349
+ # Get data for bitmap files
350
+ #
351
+ def self.get_image_data(handle)
352
+ buf = nil
353
+
354
+ begin
355
+ ptr = GlobalLock(handle)
356
+ buf_size = GlobalSize(handle)
357
+
358
+ bmi = BITMAPINFO.new(ptr)
359
+
360
+ size_image = bmi[:bmiHeader][:biSizeImage]
361
+ size_image = buf_size + 16 if size_image == 0
362
+
363
+ # Calculate the header size
364
+ case bmi[:bmiHeader][:biBitCount]
365
+ when 1
366
+ table_size = 2
367
+ when 4
368
+ table_size = 16
369
+ when 8
370
+ table_size = 256
371
+ when 16, 32
372
+ if bmi[:bmiHeader][:biCompression] == 0
373
+ table_size = bmi[:bmiHeader][:biClrUsed]
374
+ elsif compression == 3
375
+ table_size = 3
376
+ else
377
+ raise "invalid bit/compression combination"
378
+ end
379
+ when 24
380
+ table_size = bmi[:bmiHeader][:biClrUsed]
381
+ else
382
+ raise "invalid bit count"
383
+ end # case
384
+
385
+ # TODO: Document what's happening here.
386
+ offset = 0x36 + (table_size * 4)
387
+
388
+ buf = "\x42\x4D" + [size_image].pack('L') + 0.chr * 4 + [offset].pack('L') + ptr.read_bytes(buf_size)
389
+ ensure
390
+ GlobalUnlock(handle) if handle
391
+ end
392
+
393
+ buf
394
+ end
395
+
396
+ # Get and return an array of file names that have been copied.
397
+ #
398
+ def self.get_file_list(handle)
399
+ array = []
400
+ count = DragQueryFileA(handle, 0xFFFFFFFF, nil, 0)
401
+
402
+ 0.upto(count - 1){ |i|
403
+ length = DragQueryFileA(handle, i, nil, 0) + 1
404
+ buf = FFI::MemoryPointer.new(:char, length)
405
+ DragQueryFileA(handle, i, buf, buf.size)
406
+ array << buf.read_string
407
+ }
408
+
409
+ array
410
+ end
411
+ end
412
+ end
413
+
414
+ require File.join(File.dirname(__FILE__), 'html_clipboard')