clipboard_mac.mm revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "ui/base/clipboard/clipboard.h" 6 7#import <Cocoa/Cocoa.h> 8 9#include "base/basictypes.h" 10#include "base/file_path.h" 11#include "base/logging.h" 12#include "base/mac/mac_util.h" 13#include "base/mac/scoped_cftyperef.h" 14#include "base/memory/scoped_nsobject.h" 15#include "base/stl_util.h" 16#include "base/sys_string_conversions.h" 17#include "base/utf_string_conversions.h" 18#import "third_party/mozilla/NSPasteboard+Utils.h" 19#include "third_party/skia/include/core/SkBitmap.h" 20#include "ui/base/clipboard/custom_data_helper.h" 21#include "ui/gfx/canvas.h" 22#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 23#include "ui/gfx/size.h" 24 25namespace ui { 26 27namespace { 28 29// Would be nice if this were in UTCoreTypes.h, but it isn't 30NSString* const kUTTypeURLName = @"public.url-name"; 31 32// Tells us if WebKit was the last to write to the pasteboard. There's no 33// actual data associated with this type. 34NSString* const kWebSmartPastePboardType = @"NeXT smart paste pasteboard type"; 35 36// Pepper custom data format type. 37NSString* const kPepperCustomDataPboardType = 38 @"org.chromium.pepper-custom-data"; 39 40NSPasteboard* GetPasteboard() { 41 // The pasteboard should not be nil in a UI session, but this handy DCHECK 42 // can help track down problems if someone tries using clipboard code outside 43 // of a UI session. 44 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; 45 DCHECK(pasteboard); 46 return pasteboard; 47} 48 49} // namespace 50 51Clipboard::FormatType::FormatType() : data_(nil) { 52} 53 54Clipboard::FormatType::FormatType(NSString* native_format) 55 : data_([native_format retain]) { 56} 57 58Clipboard::FormatType::FormatType(const FormatType& other) 59 : data_([other.data_ retain]) { 60} 61 62Clipboard::FormatType& Clipboard::FormatType::operator=( 63 const FormatType& other) { 64 if (this != &other) { 65 [data_ release]; 66 data_ = [other.data_ retain]; 67 } 68 return *this; 69} 70 71Clipboard::FormatType::~FormatType() { 72 [data_ release]; 73} 74 75std::string Clipboard::FormatType::Serialize() const { 76 return base::SysNSStringToUTF8(data_); 77} 78 79// static 80Clipboard::FormatType Clipboard::FormatType::Deserialize( 81 const std::string& serialization) { 82 return FormatType(base::SysUTF8ToNSString(serialization)); 83} 84 85Clipboard::Clipboard() { 86 DCHECK(CalledOnValidThread()); 87} 88 89Clipboard::~Clipboard() { 90 DCHECK(CalledOnValidThread()); 91} 92 93void Clipboard::WriteObjects(Buffer buffer, const ObjectMap& objects) { 94 DCHECK(CalledOnValidThread()); 95 DCHECK_EQ(buffer, BUFFER_STANDARD); 96 97 NSPasteboard* pb = GetPasteboard(); 98 [pb declareTypes:[NSArray array] owner:nil]; 99 100 for (ObjectMap::const_iterator iter = objects.begin(); 101 iter != objects.end(); ++iter) { 102 DispatchObject(static_cast<ObjectType>(iter->first), iter->second); 103 } 104} 105 106void Clipboard::WriteText(const char* text_data, size_t text_len) { 107 std::string text_str(text_data, text_len); 108 NSString *text = base::SysUTF8ToNSString(text_str); 109 NSPasteboard* pb = GetPasteboard(); 110 [pb addTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; 111 [pb setString:text forType:NSStringPboardType]; 112} 113 114void Clipboard::WriteHTML(const char* markup_data, 115 size_t markup_len, 116 const char* url_data, 117 size_t url_len) { 118 // We need to mark it as utf-8. (see crbug.com/11957) 119 std::string html_fragment_str("<meta charset='utf-8'>"); 120 html_fragment_str.append(markup_data, markup_len); 121 NSString *html_fragment = base::SysUTF8ToNSString(html_fragment_str); 122 123 // TODO(avi): url_data? 124 NSPasteboard* pb = GetPasteboard(); 125 [pb addTypes:[NSArray arrayWithObject:NSHTMLPboardType] owner:nil]; 126 [pb setString:html_fragment forType:NSHTMLPboardType]; 127} 128 129void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) { 130 WriteData(GetRtfFormatType(), rtf_data, data_len); 131} 132 133void Clipboard::WriteBookmark(const char* title_data, 134 size_t title_len, 135 const char* url_data, 136 size_t url_len) { 137 std::string title_str(title_data, title_len); 138 NSString *title = base::SysUTF8ToNSString(title_str); 139 std::string url_str(url_data, url_len); 140 NSString *url = base::SysUTF8ToNSString(url_str); 141 142 // TODO(playmobil): In the Windows version of this function, an HTML 143 // representation of the bookmark is also added to the clipboard, to support 144 // drag and drop of web shortcuts. I don't think we need to do this on the 145 // Mac, but we should double check later on. 146 NSURL* nsurl = [NSURL URLWithString:url]; 147 148 NSPasteboard* pb = GetPasteboard(); 149 // passing UTIs into the pasteboard methods is valid >= 10.5 150 [pb addTypes:[NSArray arrayWithObjects:NSURLPboardType, 151 kUTTypeURLName, 152 nil] 153 owner:nil]; 154 [nsurl writeToPasteboard:pb]; 155 [pb setString:title forType:kUTTypeURLName]; 156} 157 158void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { 159 const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data); 160 161 // Safe because the image goes away before the call returns. 162 base::mac::ScopedCFTypeRef<CFDataRef> data( 163 CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, 164 reinterpret_cast<const UInt8*>(pixel_data), 165 size->width()*size->height()*4, 166 kCFAllocatorNull)); 167 168 base::mac::ScopedCFTypeRef<CGDataProviderRef> data_provider( 169 CGDataProviderCreateWithCFData(data)); 170 171 base::mac::ScopedCFTypeRef<CGImageRef> cgimage( 172 CGImageCreate(size->width(), 173 size->height(), 174 8, 175 32, 176 size->width()*4, 177 base::mac::GetSRGBColorSpace(), // TODO(avi): do better 178 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, 179 data_provider, 180 NULL, 181 false, 182 kCGRenderingIntentDefault)); 183 // Aggressively free storage since image buffers can potentially be very 184 // large. 185 data_provider.reset(); 186 data.reset(); 187 188 scoped_nsobject<NSBitmapImageRep> bitmap( 189 [[NSBitmapImageRep alloc] initWithCGImage:cgimage]); 190 cgimage.reset(); 191 192 scoped_nsobject<NSImage> image([[NSImage alloc] init]); 193 [image addRepresentation:bitmap]; 194 195 // An API to ask the NSImage to write itself to the clipboard comes in 10.6 :( 196 // For now, spit out the image as a TIFF. 197 NSPasteboard* pb = GetPasteboard(); 198 [pb addTypes:[NSArray arrayWithObject:NSTIFFPboardType] owner:nil]; 199 NSData *tiff_data = [image TIFFRepresentation]; 200 LOG_IF(ERROR, tiff_data == NULL) << "Failed to allocate image for clipboard"; 201 if (tiff_data) { 202 [pb setData:tiff_data forType:NSTIFFPboardType]; 203 } 204} 205 206void Clipboard::WriteData(const FormatType& format, 207 const char* data_data, 208 size_t data_len) { 209 NSPasteboard* pb = GetPasteboard(); 210 [pb addTypes:[NSArray arrayWithObject:format.ToNSString()] owner:nil]; 211 [pb setData:[NSData dataWithBytes:data_data length:data_len] 212 forType:format.ToNSString()]; 213} 214 215// Write an extra flavor that signifies WebKit was the last to modify the 216// pasteboard. This flavor has no data. 217void Clipboard::WriteWebSmartPaste() { 218 NSPasteboard* pb = GetPasteboard(); 219 NSString* format = GetWebKitSmartPasteFormatType().ToNSString(); 220 [pb addTypes:[NSArray arrayWithObject:format] owner:nil]; 221 [pb setData:nil forType:format]; 222} 223 224uint64 Clipboard::GetSequenceNumber(Buffer buffer) { 225 DCHECK(CalledOnValidThread()); 226 DCHECK_EQ(buffer, BUFFER_STANDARD); 227 228 NSPasteboard* pb = GetPasteboard(); 229 return [pb changeCount]; 230} 231 232bool Clipboard::IsFormatAvailable(const FormatType& format, 233 Buffer buffer) const { 234 DCHECK(CalledOnValidThread()); 235 DCHECK_EQ(buffer, BUFFER_STANDARD); 236 237 NSPasteboard* pb = GetPasteboard(); 238 NSArray* types = [pb types]; 239 240 // Safari only places RTF on the pasteboard, never HTML. We can convert RTF 241 // to HTML, so the presence of either indicates success when looking for HTML. 242 if ([format.ToNSString() isEqualToString:NSHTMLPboardType]) { 243 return [types containsObject:NSHTMLPboardType] || 244 [types containsObject:NSRTFPboardType]; 245 } 246 return [types containsObject:format.ToNSString()]; 247} 248 249void Clipboard::Clear(Buffer buffer) { 250 DCHECK(CalledOnValidThread()); 251 DCHECK_EQ(buffer, BUFFER_STANDARD); 252 253 NSPasteboard* pb = GetPasteboard(); 254 [pb declareTypes:[NSArray array] owner:nil]; 255} 256 257void Clipboard::ReadAvailableTypes(Clipboard::Buffer buffer, 258 std::vector<string16>* types, 259 bool* contains_filenames) const { 260 DCHECK(CalledOnValidThread()); 261 types->clear(); 262 if (IsFormatAvailable(Clipboard::GetPlainTextFormatType(), buffer)) 263 types->push_back(UTF8ToUTF16(kMimeTypeText)); 264 if (IsFormatAvailable(Clipboard::GetHtmlFormatType(), buffer)) 265 types->push_back(UTF8ToUTF16(kMimeTypeHTML)); 266 if (IsFormatAvailable(Clipboard::GetRtfFormatType(), buffer)) 267 types->push_back(UTF8ToUTF16(kMimeTypeRTF)); 268 if ([NSImage canInitWithPasteboard:GetPasteboard()]) 269 types->push_back(UTF8ToUTF16(kMimeTypePNG)); 270 *contains_filenames = false; 271 272 NSPasteboard* pb = GetPasteboard(); 273 if ([[pb types] containsObject:kWebCustomDataPboardType]) { 274 NSData* data = [pb dataForType:kWebCustomDataPboardType]; 275 if ([data length]) 276 ReadCustomDataTypes([data bytes], [data length], types); 277 } 278} 279 280void Clipboard::ReadText(Clipboard::Buffer buffer, string16* result) const { 281 DCHECK(CalledOnValidThread()); 282 DCHECK_EQ(buffer, BUFFER_STANDARD); 283 NSPasteboard* pb = GetPasteboard(); 284 NSString* contents = [pb stringForType:NSStringPboardType]; 285 286 UTF8ToUTF16([contents UTF8String], 287 [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding], 288 result); 289} 290 291void Clipboard::ReadAsciiText(Clipboard::Buffer buffer, 292 std::string* result) const { 293 DCHECK(CalledOnValidThread()); 294 DCHECK_EQ(buffer, BUFFER_STANDARD); 295 NSPasteboard* pb = GetPasteboard(); 296 NSString* contents = [pb stringForType:NSStringPboardType]; 297 298 if (!contents) 299 result->clear(); 300 else 301 result->assign([contents UTF8String]); 302} 303 304void Clipboard::ReadHTML(Clipboard::Buffer buffer, string16* markup, 305 std::string* src_url, uint32* fragment_start, 306 uint32* fragment_end) const { 307 DCHECK(CalledOnValidThread()); 308 DCHECK_EQ(buffer, BUFFER_STANDARD); 309 310 // TODO(avi): src_url? 311 markup->clear(); 312 if (src_url) 313 src_url->clear(); 314 315 NSPasteboard* pb = GetPasteboard(); 316 NSArray* supportedTypes = [NSArray arrayWithObjects:NSHTMLPboardType, 317 NSRTFPboardType, 318 NSStringPboardType, 319 nil]; 320 NSString* bestType = [pb availableTypeFromArray:supportedTypes]; 321 if (bestType) { 322 NSString* contents = [pb stringForType:bestType]; 323 if ([bestType isEqualToString:NSRTFPboardType]) 324 contents = [pb htmlFromRtf]; 325 UTF8ToUTF16([contents UTF8String], 326 [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding], 327 markup); 328 } 329 330 *fragment_start = 0; 331 DCHECK(markup->length() <= kuint32max); 332 *fragment_end = static_cast<uint32>(markup->length()); 333} 334 335void Clipboard::ReadRTF(Buffer buffer, std::string* result) const { 336 DCHECK(CalledOnValidThread()); 337 DCHECK_EQ(buffer, BUFFER_STANDARD); 338 339 return ReadData(GetRtfFormatType(), result); 340} 341 342SkBitmap Clipboard::ReadImage(Buffer buffer) const { 343 DCHECK(CalledOnValidThread()); 344 DCHECK_EQ(buffer, BUFFER_STANDARD); 345 346 scoped_nsobject<NSImage> image( 347 [[NSImage alloc] initWithPasteboard:GetPasteboard()]); 348 if (!image.get()) 349 return SkBitmap(); 350 351 gfx::ScopedNSGraphicsContextSaveGState scoped_state; 352 [image setFlipped:YES]; 353 int width = [image size].width; 354 int height = [image size].height; 355 356 gfx::Canvas canvas(gfx::Size(width, height), ui::SCALE_FACTOR_100P, false); 357 { 358 skia::ScopedPlatformPaint scoped_platform_paint(canvas.sk_canvas()); 359 CGContextRef gc = scoped_platform_paint.GetPlatformSurface(); 360 NSGraphicsContext* cocoa_gc = 361 [NSGraphicsContext graphicsContextWithGraphicsPort:gc flipped:NO]; 362 [NSGraphicsContext setCurrentContext:cocoa_gc]; 363 [image drawInRect:NSMakeRect(0, 0, width, height) 364 fromRect:NSZeroRect 365 operation:NSCompositeCopy 366 fraction:1.0]; 367 } 368 return canvas.ExtractImageRep().sk_bitmap(); 369} 370 371void Clipboard::ReadCustomData(Buffer buffer, 372 const string16& type, 373 string16* result) const { 374 DCHECK(CalledOnValidThread()); 375 DCHECK_EQ(buffer, BUFFER_STANDARD); 376 377 NSPasteboard* pb = GetPasteboard(); 378 if ([[pb types] containsObject:kWebCustomDataPboardType]) { 379 NSData* data = [pb dataForType:kWebCustomDataPboardType]; 380 if ([data length]) 381 ReadCustomDataForType([data bytes], [data length], type, result); 382 } 383} 384 385void Clipboard::ReadBookmark(string16* title, std::string* url) const { 386 DCHECK(CalledOnValidThread()); 387 NSPasteboard* pb = GetPasteboard(); 388 389 if (title) { 390 NSString* contents = [pb stringForType:kUTTypeURLName]; 391 UTF8ToUTF16([contents UTF8String], 392 [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding], 393 title); 394 } 395 396 if (url) { 397 NSString* url_string = [[NSURL URLFromPasteboard:pb] absoluteString]; 398 if (!url_string) 399 url->clear(); 400 else 401 url->assign([url_string UTF8String]); 402 } 403} 404 405void Clipboard::ReadData(const FormatType& format, std::string* result) const { 406 DCHECK(CalledOnValidThread()); 407 NSPasteboard* pb = GetPasteboard(); 408 NSData* data = [pb dataForType:format.ToNSString()]; 409 if ([data length]) 410 result->assign(static_cast<const char*>([data bytes]), [data length]); 411} 412 413// static 414Clipboard::FormatType Clipboard::GetFormatType( 415 const std::string& format_string) { 416 return FormatType::Deserialize(format_string); 417} 418 419// static 420const Clipboard::FormatType& Clipboard::GetUrlFormatType() { 421 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSURLPboardType)); 422 return type; 423} 424 425// static 426const Clipboard::FormatType& Clipboard::GetUrlWFormatType() { 427 return GetUrlFormatType(); 428} 429 430// static 431const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { 432 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSStringPboardType)); 433 return type; 434} 435 436// static 437const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { 438 return GetPlainTextFormatType(); 439} 440 441// static 442const Clipboard::FormatType& Clipboard::GetFilenameFormatType() { 443 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSFilenamesPboardType)); 444 return type; 445} 446 447// static 448const Clipboard::FormatType& Clipboard::GetFilenameWFormatType() { 449 return GetFilenameFormatType(); 450} 451 452// static 453const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { 454 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSHTMLPboardType)); 455 return type; 456} 457 458// static 459const Clipboard::FormatType& Clipboard::GetRtfFormatType() { 460 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSRTFPboardType)); 461 return type; 462} 463 464// static 465const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { 466 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSTIFFPboardType)); 467 return type; 468} 469 470// static 471const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { 472 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebSmartPastePboardType)); 473 return type; 474} 475 476// static 477const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { 478 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebCustomDataPboardType)); 479 return type; 480} 481 482// static 483const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() { 484 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPepperCustomDataPboardType)); 485 return type; 486} 487 488} // namespace ui 489