Class: Pasteboard

Inherits:
Object
  • Object
show all
Defined in:
lib/pasteboard.rb,
ext/pasteboard/pasteboard.c

Overview

Pasteboard wraps the OS X pasteboard (clipboard) allowing you to paste multiple flavors of a pasteboard item. Currently it only supports the general clipboard.

To add data to the clipboard:

item = [
  Pasteboard::Type::UTF_8,      'π'],
  Pasteboard::Type::PLAIN_TEXT, 'pi'],
]

pasteboard.put item

See also #put, #put_url and #put_jpeg_url

To retrieve data from the clipboard:

data = pasteboard.first Pasteboard::Type::UTF_8

If the item cannot be found nil will be returned.

See also #first, #[] and #each.

Pasteboard also provides direct access to the C API through #clear, #copy_item_flavors, #copy_item_flavor_data, #get_item_count, #get_item_identifier, #name, #put_item_flavor and #sync.

Defined Under Namespace

Modules: Type Classes: Error, Missing

Constant Summary collapse

VERSION =

Version of pasteboard

'1.0'
BE_BOM =

:nodoc:

"\xFE\xFF"
LE_BOM =

:nodoc:

"\xFF\xFE"
NATIVE_BOM =
little ? LE_BOM             : BE_BOM
NATIVE_ENCODING =
little ? Encoding::UTF_16LE : Encoding::UTF_16BE
CLIPBOARD =

General clipboard pasteboard type. Cut, copy and paste use this pasteboard.

'com.apple.pasteboard.clipboard'
FIND =

Find pasteboard type. Find and find and replace use this pasteboard.

'com.apple.pasteboard.find'
UNIQUE =

A uniquely named pasteboard type.

nil
MODIFIED =
ULONG2NUM(kPasteboardModified)
CLIENT_IS_OWNER =
ULONG2NUM(kPasteboardClientIsOwner)

Instance Method Summary collapse

Constructor Details

#type( = Pasteboard::CLIPBOARD) ⇒ Object

Creates a new pasteboard of the specified type.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'ext/pasteboard/pasteboard.c', line 143

static VALUE
pb_init(int argc, VALUE* argv, VALUE self) {
  OSStatus err = noErr;
  PasteboardRef pasteboard;
  CFStringRef pasteboard_type = NULL;
  VALUE type = Qnil;

  if (argc == 0) {
    pasteboard_type = kPasteboardClipboard;
  } else {
    rb_scan_args(argc, argv, "01", &type);
  }

  if (!NIL_P(type)) {
    pasteboard_type = CFStringCreateWithCString(NULL,
        value_to_utf8_cstr(type),
        kCFStringEncodingUTF8);

    if (pasteboard_type == NULL)
      rb_raise(ePBError, "unable to allocate memory for pasteboard type");
  }

  err = PasteboardCreate(pasteboard_type, &pasteboard);

  if (pasteboard_type)
    CFRelease(pasteboard_type);

  pb_error(err);

  DATA_PTR(self) = (void *)pasteboard;

  return self;
}

Instance Method Details

#[](index, flavor = nil) ⇒ Object

Synchronizes the pasteboard and returns the item at index in the pasteboard.

If flavor is given only the given flavor’s data is returned. If no flavor matches nil is returned.

An item is an Array of pairs in the order of preference which looks like this:

[
  ["public.utf8-plain-text", "Pasteboard"],
  ["public.utf16-external-plain-text",
   "\377\376P\000a\000s\000t\000e\000b\000o\000a\000r\000d\000"],
  ["com.apple.traditional-mac-plain-text", "Pasteboard"],
  ["public.utf16-plain-text",
   "P\000a\000s\000t\000e\000b\000o\000a\000r\000d\000"],
]


98
99
100
101
102
103
104
105
106
107
108
# File 'lib/pasteboard.rb', line 98

def [] index, flavor = nil
  flags = sync

  raise Error, 'pasteboard sync error' if (flags & MODIFIED) != 0

  id = get_item_identifier index + 1

  get id, flavor
rescue Missing
  return nil
end

#clearObject

Clears the contents of the pasteboard.



183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'ext/pasteboard/pasteboard.c', line 183

static VALUE
pb_clear(VALUE self) {
  OSStatus err = noErr;
  PasteboardRef pasteboard;

  pasteboard = pb_get_pasteboard(self);

  err = PasteboardClear(pasteboard);

  pb_error(err);

  return self;
}

#identifierObject

Retrieves pasteboard data from identifier of flavor



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'ext/pasteboard/pasteboard.c', line 242

static VALUE
pb_copy_item_flavor_data(VALUE self, VALUE identifier, VALUE flavor) {
  OSStatus err = noErr;
  PasteboardRef pasteboard;
  PasteboardItemID id = 0;
  CFIndex data_length = 0;
  CFDataRef flavor_data = NULL;
  CFStringRef flavor_type = NULL;
  UInt8 *buffer = NULL;
  VALUE data = Qnil;
  VALUE encoding = Qnil;

  pasteboard = pb_get_pasteboard(self);

  id = (PasteboardItemID)NUM2ULONG(identifier);

  flavor_type = CFStringCreateWithCString(NULL,
      value_to_usascii_cstr(flavor),
      kCFStringEncodingASCII);

  if (flavor_type == NULL)
    rb_raise(ePBError, "unable to allocate memory for flavor type");

  err = PasteboardCopyItemFlavorData(pasteboard, id, flavor_type, &flavor_data);

  pb_error(err);

  data_length = CFDataGetLength(flavor_data);

  buffer = (UInt8 *)malloc(data_length);

  if (buffer == NULL) {
    CFRelease(flavor_data);
    rb_raise(ePBError, "unable to allocate memory for data");
  }

  CFDataGetBytes(flavor_data, CFRangeMake(0, data_length), buffer);

  CFRelease(flavor_data);

  data = rb_str_new((char *)buffer, data_length);

  free(buffer);

#if HAVE_RB_STR_ENCODE
  encoding = rb_hash_aref(cPBTypeEncodings, flavor);

  if (rb_str_equal(flavor, utf16_external_flavor) ||
      rb_str_equal(flavor, utf16_internal_flavor)) {
    handle_bom(data, encoding);
  } else {
    rb_enc_associate(data, rb_to_encoding(encoding));
  }
#endif

  return data;
}

#identifierObject

Returns an Array of flavors for the pasteboard item at identifier.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'ext/pasteboard/pasteboard.c', line 203

static VALUE
pb_copy_item_flavors(VALUE self, VALUE identifier) {
  OSStatus err = noErr;
  PasteboardRef pasteboard;
  PasteboardItemID item_id;
  CFArrayRef flavor_types = NULL;
  CFIndex i, flavor_count = 0;
  VALUE flavors = Qnil;

  item_id = (PasteboardItemID)NUM2ULONG(identifier);

  pasteboard = pb_get_pasteboard(self);

  err = PasteboardCopyItemFlavors(pasteboard, item_id, &flavor_types);

  pb_error(err);

  flavors = rb_ary_new();

  flavor_count = CFArrayGetCount(flavor_types);

  for (i = 0; i < flavor_count; i++) {
    CFStringRef flavor_type =
      (CFStringRef)CFArrayGetValueAtIndex(flavor_types, i);

    rb_ary_push(flavors, string_ref_to_value(flavor_type));
  }

  CFRelease(flavor_types);

  return flavors;
}

#each(flavor = nil) ⇒ Object

Synchronizes the pasteboard and yields each item in the pasteboard.

If flavor is given only the given flavor’s data is yielded. If no flavor matches nil is yielded.

See #[] for a description of an item.

Raises:



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/pasteboard.rb', line 118

def each flavor = nil # :yields: item
  unless block_given? then
    enum = defined?(Enumerator) ? Enumerator : Enumerable::Enumerator # 1.8.7
    return enum.new(self, :each, flavor)
  end

  flags = sync

  raise Error, 'pasteboard sync error' if (flags & MODIFIED) != 0

  ids.each do |id|
    yield get(id, flavor)
  end

  self
end

#first(flavor = nil) ⇒ Object

Retrieves the first item in the pasteboard.

If flavor is given only the given flavor’s data is returned. If no flavor matches nil is returned.

See #[] for a description of an item.



143
144
145
# File 'lib/pasteboard.rb', line 143

def first flavor = nil
  self[0, flavor]
end

#get(id, flavor = nil) ⇒ Object

Returns the item with id in the pasteboard.

If flavor is given only the given flavor’s data is returned. If no flavor matches nil is returned.

See #[] for a description of an item.



155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/pasteboard.rb', line 155

def get id, flavor = nil
  item = copy_item_flavors(id).map do |item_flavor|
    if flavor then
      return copy_item_flavor_data(id, item_flavor) if item_flavor == flavor
      next
    end

    [item_flavor, copy_item_flavor_data(id, item_flavor)]
  end

  return nil if item.compact.empty?

  item
end

#get_item_countObject

The number of items on the pasteboard



306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'ext/pasteboard/pasteboard.c', line 306

static VALUE
pb_get_item_count(VALUE self) {
  OSStatus err = noErr;
  PasteboardRef pasteboard;
  ItemCount item_count = 0;

  pasteboard = pb_get_pasteboard(self);

  err = PasteboardGetItemCount(pasteboard, &item_count);

  pb_error(err);

  return ULONG2NUM(item_count);
}

#indexObject

The identifier of the pasteboard item at index which is 1-based.



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'ext/pasteboard/pasteboard.c', line 327

static VALUE
pb_get_item_identifier(VALUE self, VALUE index) {
  OSStatus err = noErr;
  PasteboardRef pasteboard;
  CFIndex item_index = 0;
  PasteboardItemID item_id = 0;

  item_index = NUM2ULONG(index);

  pasteboard = pb_get_pasteboard(self);

  err = PasteboardGetItemIdentifier(pasteboard, item_index, &item_id);

  pb_error(err);

  return ULONG2NUM((unsigned long)item_id);
}

#idsObject

An array of item ids in the pasteboard. You must sync the clipboard to get the latest ids.



174
175
176
177
178
# File 'lib/pasteboard.rb', line 174

def ids
  (1..get_item_count).map do |index|
    get_item_identifier index
  end
end

#inspectObject

:nodoc:



180
181
182
# File 'lib/pasteboard.rb', line 180

def inspect # :nodoc:
  '#<%s:0x%x %s>' % [self.class, object_id, name]
end

#nameObject

The name of this pasteboard.



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'ext/pasteboard/pasteboard.c', line 351

static VALUE
pb_name(VALUE self) {
  OSStatus err = noErr;
  PasteboardRef pasteboard;
  CFStringRef pasteboard_name = NULL;
  VALUE name = Qnil;

  pasteboard = pb_get_pasteboard(self);

  err = PasteboardCopyName(pasteboard, &pasteboard_name);

  pb_error(err);

  name = string_ref_to_value(pasteboard_name);

  if (pasteboard_name)
    CFRelease(pasteboard_name);

  return name;
}

#put(*items) ⇒ Object

Clears the pasteboard and adds items to the pasteboard. Each item is added with a consecutive id starting at 0.

An item must be an Enumerable with pairs of item flavors and items. For example:

item = [
  [Pasteboard::Type::TIFF,     tiff],
  [Pasteboard::Type::URL,      url],
  [Pasteboard::Type::URL_NAME, url],
  [Pasteboard::Type::UTF_8,    url],
]

pasteboard.put item

The pasteboard considers flavors added earlier in an item to have a higher preference.

Raises:



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/pasteboard.rb', line 203

def put *items
  clear
  flags = sync

  raise Error, 'pasteboard sync error' if (flags & MODIFIED)        != 0
  raise Error, 'pasteboard not owned'  if (flags & CLIENT_IS_OWNER) == 0

  items.each_with_index do |item, id|
    item.each do |flavor, data|
      put_item_flavor id, flavor, data
    end
  end

  self
end

#idObject

Puts an item into the pasteboard. id is used to identify an item, flavor is the item’s type and data is the pasteboard data for the item.



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'ext/pasteboard/pasteboard.c', line 398

static VALUE
pb_put_item_flavor(VALUE self, VALUE id, VALUE flavor, VALUE data) {
  OSStatus err = noErr;
  PasteboardRef pasteboard;
  CFDataRef pasteboard_data = NULL;
  CFStringRef item_flavor = NULL;

  pasteboard = pb_get_pasteboard(self);

  item_flavor = CFStringCreateWithCString(NULL,
      value_to_usascii_cstr(flavor),
      kCFStringEncodingASCII);

  if (item_flavor == NULL)
    rb_raise(ePBError, "unable to allocate memory for item flavor");

  pasteboard_data = CFDataCreate(kCFAllocatorDefault,
      (UInt8 *)StringValuePtr(data), RSTRING_LEN(data));

  if (pasteboard_data == NULL) {
    CFRelease(item_flavor);
    rb_raise(ePBError, "unable to allocate memory for pasteboard data");
  }

  err = PasteboardPutItemFlavor(pasteboard,
      (PasteboardItemID)NUM2ULONG(id),
      item_flavor,
      pasteboard_data,
      kPasteboardFlavorNoFlags);

  CFRelease(item_flavor);
  CFRelease(pasteboard_data);

  pb_error(err);

  return self;
}

#put_jpeg_url(image, url, url_name = url) ⇒ Object

Adds JPEG data image to the pasteboard with a url and optional url_name.



236
237
238
239
240
241
242
243
244
245
# File 'lib/pasteboard.rb', line 236

def put_jpeg_url image, url, url_name = url
  item = [
    [Pasteboard::Type::JPEG,     image],
    [Pasteboard::Type::URL,      url],
    [Pasteboard::Type::URL_NAME, url_name],
    [Pasteboard::Type::UTF_8,    url],
  ]

  put item
end

#put_url(url, url_name = url) ⇒ Object

Adds url to the pasteboard with an optional url_name.



222
223
224
225
226
227
228
229
230
# File 'lib/pasteboard.rb', line 222

def put_url url, url_name = url
  item = [
    [Pasteboard::Type::URL,      url],
    [Pasteboard::Type::URL_NAME, url_name],
    [Pasteboard::Type::UTF_8,    url],
  ]

  put item
end

#syncObject

Synchronizes the local pasteboard to reflect the contents of the global pasteboard.



379
380
381
382
383
384
385
386
387
388
389
# File 'ext/pasteboard/pasteboard.c', line 379

static VALUE
pb_sync(VALUE self) {
  PasteboardSyncFlags flags;
  PasteboardRef pasteboard;

  pasteboard = pb_get_pasteboard(self);

  flags = PasteboardSynchronize(pasteboard);

  return ULONG2NUM(flags);
}