1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Mozilla Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2006
20 * the Initial Developer. All Rights Reserved.
23 * Josh Aas <josh@mozilla.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
40 #include "nsClipboard.h"
42 #include "nsISupportsPrimitives.h"
43 #include "nsXPIDLString.h"
44 #include "nsPrimitiveHelpers.h"
47 #include "nsILocalFile.h"
48 #include "nsStringStream.h"
49 #include "nsDragService.h"
51 #include "nsPrintfCString.h"
52 #include "nsObjCExceptions.h"
54 // Screenshots use the (undocumented) png pasteboard type.
55 #define IMAGE_PASTEBOARD_TYPES NSTIFFPboardType, @"Apple PNG pasteboard type", nil
63 extern PRLogModuleInfo* sCocoaLog;
67 nsClipboard::nsClipboard() : nsBaseClipboard()
73 nsClipboard::~nsClipboard()
78 // We separate this into its own function because after an @try, all local
79 // variables within that function get marked as volatile, and our C++ type
80 // system doesn't like volatile things.
82 GetDataFromPasteboard(NSPasteboard* aPasteboard, NSString* aType)
86 data = [aPasteboard dataForType:aType];
87 } @catch (NSException* e) {
88 NS_WARNING(nsPrintfCString(256, "Exception raised while getting data from the pasteboard: \"%s - %s\"",
89 [[e name] UTF8String], [[e reason] UTF8String]).get());
96 nsClipboard::SetNativeClipboardData(PRInt32 aWhichClipboard)
98 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
100 if ((aWhichClipboard != kGlobalClipboard) || !mTransferable)
101 return NS_ERROR_FAILURE;
103 mIgnoreEmptyNotification = PR_TRUE;
105 NSDictionary* pasteboardOutputDict = PasteboardDictFromTransferable(mTransferable);
106 if (!pasteboardOutputDict)
107 return NS_ERROR_FAILURE;
109 // write everything out to the general pasteboard
110 unsigned int outputCount = [pasteboardOutputDict count];
111 NSArray* outputKeys = [pasteboardOutputDict allKeys];
112 NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
113 [generalPBoard declareTypes:outputKeys owner:nil];
114 for (unsigned int i = 0; i < outputCount; i++) {
115 NSString* currentKey = [outputKeys objectAtIndex:i];
116 id currentValue = [pasteboardOutputDict valueForKey:currentKey];
117 if (currentKey == NSStringPboardType ||
118 currentKey == NSHTMLPboardType ||
119 currentKey == kCorePboardType_url ||
120 currentKey == kCorePboardType_urld ||
121 currentKey == kCorePboardType_urln)
122 [generalPBoard setString:currentValue forType:currentKey];
124 [generalPBoard setData:currentValue forType:currentKey];
127 mChangeCount = [generalPBoard changeCount];
129 mIgnoreEmptyNotification = PR_FALSE;
133 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
138 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, PRInt32 aWhichClipboard)
140 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
142 if ((aWhichClipboard != kGlobalClipboard) || !aTransferable)
143 return NS_ERROR_FAILURE;
145 NSPasteboard* cocoaPasteboard = [NSPasteboard generalPasteboard];
146 if (!cocoaPasteboard)
147 return NS_ERROR_FAILURE;
149 // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
150 nsCOMPtr<nsISupportsArray> flavorList;
151 nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
153 return NS_ERROR_FAILURE;
155 PRUint32 flavorCount;
156 flavorList->Count(&flavorCount);
158 // If we were the last ones to put something on the pasteboard, then just use the cached
159 // transferable. Otherwise clear it because it isn't relevant any more.
160 if (mChangeCount == [cocoaPasteboard changeCount]) {
162 for (PRUint32 i = 0; i < flavorCount; i++) {
163 nsCOMPtr<nsISupports> genericFlavor;
164 flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
165 nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
169 nsXPIDLCString flavorStr;
170 currentFlavor->ToString(getter_Copies(flavorStr));
172 nsCOMPtr<nsISupports> dataSupports;
173 PRUint32 dataSize = 0;
174 rv = mTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
175 if (NS_SUCCEEDED(rv)) {
176 aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
177 return NS_OK; // maybe try to fill in more types? Is there a point?
183 nsBaseClipboard::EmptyClipboard(kGlobalClipboard);
186 // at this point we can't satisfy the request from cache data so let's look
187 // for things other people put on the system clipboard
189 for (PRUint32 i = 0; i < flavorCount; i++) {
190 nsCOMPtr<nsISupports> genericFlavor;
191 flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
192 nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
196 nsXPIDLCString flavorStr;
197 currentFlavor->ToString(getter_Copies(flavorStr)); // i has a flavr
199 // printf("looking for clipboard data of type %s\n", flavorStr.get());
201 const NSString *pboardType;
202 if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
203 NSString* pString = [cocoaPasteboard stringForType:pboardType];
207 NSData* stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
208 unsigned int dataLength = [stringData length];
209 void* clipboardDataPtr = malloc(dataLength);
210 if (!clipboardDataPtr)
211 return NS_ERROR_OUT_OF_MEMORY;
212 [stringData getBytes:clipboardDataPtr];
214 // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
215 PRInt32 signedDataLength = dataLength;
216 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
217 dataLength = signedDataLength;
219 // skip BOM (Byte Order Mark to distinguish little or big endian)
220 PRUnichar* clipboardDataPtrNoBOM = (PRUnichar*)clipboardDataPtr;
221 if ((dataLength > 2) &&
222 ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
223 (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
224 dataLength -= sizeof(PRUnichar);
225 clipboardDataPtrNoBOM += 1;
228 nsCOMPtr<nsISupports> genericDataWrapper;
229 nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
230 getter_AddRefs(genericDataWrapper));
231 aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
232 free(clipboardDataPtr);
235 else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
236 flavorStr.EqualsLiteral(kPNGImageMime) ||
237 flavorStr.EqualsLiteral(kGIFImageMime)) {
238 // Figure out if there's data on the pasteboard we can grab (sanity check)
239 NSString *type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
243 // Read data off the clipboard
244 NSData *pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
248 // Figure out what type we're converting to
249 CFStringRef outputType = NULL;
250 if (flavorStr.EqualsLiteral(kJPEGImageMime))
251 outputType = CFSTR("public.jpeg");
252 else if (flavorStr.EqualsLiteral(kPNGImageMime))
253 outputType = CFSTR("public.png");
254 else if (flavorStr.EqualsLiteral(kGIFImageMime))
255 outputType = CFSTR("com.compuserve.gif");
259 // Use ImageIO to interpret the data on the clipboard and transcode.
260 // Note that ImageIO, like all CF APIs, allows NULLs to propagate freely
261 // and safely in most cases (like ObjC). A notable exception is CFRelease.
262 NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
263 (NSNumber*)kCFBooleanTrue, kCGImageSourceShouldAllowFloat,
264 (type == NSTIFFPboardType ? @"public.tiff" : @"public.png"),
265 kCGImageSourceTypeIdentifierHint, nil];
267 CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)pasteboardData,
268 (CFDictionaryRef)options);
269 NSMutableData *encodedData = [NSMutableData data];
270 CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)encodedData,
273 CGImageDestinationAddImageFromSource(dest, source, 0, NULL);
274 PRBool successfullyConverted = CGImageDestinationFinalize(dest);
276 if (successfullyConverted) {
277 // Put the converted data in a form Gecko can understand
278 nsCOMPtr<nsIInputStream> byteStream;
279 NS_NewByteInputStream(getter_AddRefs(byteStream), (const char*)[encodedData bytes],
280 [encodedData length], NS_ASSIGNMENT_COPY);
282 aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
290 if (successfullyConverted)
299 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
303 // returns true if we have *any* of the passed in flavors available for pasting
305 nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, PRUint32 aLength,
306 PRInt32 aWhichClipboard, PRBool* outResult)
308 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
310 *outResult = PR_FALSE;
312 if ((aWhichClipboard != kGlobalClipboard) || !aFlavorList)
315 // first see if we have data for this in our cached transferable
317 nsCOMPtr<nsISupportsArray> transferableFlavorList;
318 nsresult rv = mTransferable->FlavorsTransferableCanImport(getter_AddRefs(transferableFlavorList));
319 if (NS_SUCCEEDED(rv)) {
320 PRUint32 transferableFlavorCount;
321 transferableFlavorList->Count(&transferableFlavorCount);
322 for (PRUint32 j = 0; j < transferableFlavorCount; j++) {
323 nsCOMPtr<nsISupports> transferableFlavorSupports;
324 transferableFlavorList->GetElementAt(j, getter_AddRefs(transferableFlavorSupports));
325 nsCOMPtr<nsISupportsCString> currentTransferableFlavor(do_QueryInterface(transferableFlavorSupports));
326 if (!currentTransferableFlavor)
328 nsXPIDLCString transferableFlavorStr;
329 currentTransferableFlavor->ToString(getter_Copies(transferableFlavorStr));
331 for (PRUint32 k = 0; k < aLength; k++) {
332 if (transferableFlavorStr.Equals(aFlavorList[k])) {
333 *outResult = PR_TRUE;
341 NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
343 for (PRUint32 i = 0; i < aLength; i++) {
344 nsDependentCString mimeType(aFlavorList[i]);
345 const NSString *pboardType;
347 if (nsClipboard::IsStringType(mimeType, &pboardType)) {
348 NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
349 if (availableType && [availableType isEqualToString:pboardType]) {
350 *outResult = PR_TRUE;
353 } else if (!strcmp(aFlavorList[i], kJPEGImageMime) ||
354 !strcmp(aFlavorList[i], kPNGImageMime) ||
355 !strcmp(aFlavorList[i], kGIFImageMime)) {
356 NSString* availableType = [generalPBoard availableTypeFromArray:
357 [NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
359 *outResult = PR_TRUE;
367 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
371 // This function converts anything that other applications might understand into the system format
372 // and puts it into a dictionary which it returns.
375 nsClipboard::PasteboardDictFromTransferable(nsITransferable* aTransferable)
377 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
382 NSMutableDictionary* pasteboardOutputDict = [NSMutableDictionary dictionary];
384 nsCOMPtr<nsISupportsArray> flavorList;
385 nsresult rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
389 PRUint32 flavorCount;
390 flavorList->Count(&flavorCount);
391 for (PRUint32 i = 0; i < flavorCount; i++) {
392 nsCOMPtr<nsISupports> genericFlavor;
393 flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
394 nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
398 nsXPIDLCString flavorStr;
399 currentFlavor->ToString(getter_Copies(flavorStr));
401 PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("writing out clipboard data of type %s (%d)\n", flavorStr.get(), i));
403 const NSString *pboardType;
405 if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
407 PRUint32 dataSize = 0;
408 nsCOMPtr<nsISupports> genericDataWrapper;
409 rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &dataSize);
410 nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
412 NSString* nativeString = [NSString stringWithCharacters:(const unichar*)data length:(dataSize / sizeof(PRUnichar))];
413 // be nice to Carbon apps, normalize the receiver's contents using Form C.
414 nativeString = [nativeString precomposedStringWithCanonicalMapping];
416 [pasteboardOutputDict setObject:nativeString forKey:pboardType];
418 nsMemory::Free(data);
420 else if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
421 flavorStr.EqualsLiteral(kGIFImageMime) || flavorStr.EqualsLiteral(kNativeImageMime)) {
422 PRUint32 dataSize = 0;
423 nsCOMPtr<nsISupports> transferSupports;
424 aTransferable->GetTransferData(flavorStr, getter_AddRefs(transferSupports), &dataSize);
425 nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive(do_QueryInterface(transferSupports));
429 nsCOMPtr<nsISupports> primitiveData;
430 ptrPrimitive->GetData(getter_AddRefs(primitiveData));
432 nsCOMPtr<nsIImage> image(do_QueryInterface(primitiveData));
434 NS_WARNING("Image isn't an nsIImage in transferable");
438 if (NS_FAILED(image->LockImagePixels(PR_FALSE)))
441 PRInt32 height = image->GetHeight();
442 PRInt32 stride = image->GetLineStride();
443 PRInt32 width = image->GetWidth();
444 if ((stride % 4 != 0) || (height < 1) || (width < 1))
447 // Create a CGImageRef with the bits from the image, taking into account
448 // the alpha ordering and endianness of the machine so we don't have to
449 // touch the bits ourselves.
450 CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL,
454 CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
455 CGImageRef imageRef = CGImageCreate(width,
461 kCGBitmapByteOrder32Host | kCGImageAlphaFirst,
465 kCGRenderingIntentDefault);
466 CGColorSpaceRelease(colorSpace);
467 CGDataProviderRelease(dataProvider);
469 // Convert the CGImageRef to TIFF data.
470 CFMutableDataRef tiffData = CFDataCreateMutable(kCFAllocatorDefault, 0);
471 CGImageDestinationRef destRef = CGImageDestinationCreateWithData(tiffData,
472 CFSTR("public.tiff"),
475 CGImageDestinationAddImage(destRef, imageRef, NULL);
476 PRBool successfullyConverted = CGImageDestinationFinalize(destRef);
478 CGImageRelease(imageRef);
482 if (NS_FAILED(image->UnlockImagePixels(PR_FALSE)) || !successfullyConverted) {
488 [pasteboardOutputDict setObject:(NSMutableData*)tiffData forKey:NSTIFFPboardType];
492 else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
493 [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] forKey:NSFilesPromisePboardType];
495 else if (flavorStr.EqualsLiteral(kURLMime)) {
497 nsCOMPtr<nsISupports> genericURL;
498 rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericURL), &len);
499 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
502 urlObject->GetData(url);
504 // A newline embedded in the URL means that the form is actually URL + title.
505 PRInt32 newlinePos = url.FindChar(PRUnichar('\n'));
506 if (newlinePos >= 0) {
507 url.Truncate(newlinePos);
509 nsAutoString urlTitle;
510 urlObject->GetData(urlTitle);
511 urlTitle.Mid(urlTitle, newlinePos + 1, len - (newlinePos + 1));
513 NSString *nativeTitle = [[NSString alloc] initWithCharacters:urlTitle.get() length:urlTitle.Length()];
514 // be nice to Carbon apps, normalize the receiver's contents using Form C.
515 [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urln];
516 // Also put the title out as 'urld', since some recipients will look for that.
517 [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urld];
518 [nativeTitle release];
521 // The Finder doesn't like getting random binary data aka
522 // Unicode, so change it into an escaped URL containing only
524 nsCAutoString utf8Data = NS_ConvertUTF16toUTF8(url.get(), url.Length());
525 nsCAutoString escData;
526 NS_EscapeURL(utf8Data.get(), utf8Data.Length(), esc_OnlyNonASCII|esc_AlwaysCopy, escData);
528 // printf("Escaped url is %s, length %d\n", escData.get(), escData.Length());
530 NSString *nativeURL = [NSString stringWithUTF8String:escData.get()];
531 [pasteboardOutputDict setObject:nativeURL forKey:kCorePboardType_url];
533 // If it wasn't a type that we recognize as exportable we don't put it on the system
534 // clipboard. We'll just access it from our cached transferable when we need it.
537 return pasteboardOutputDict;
539 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
542 PRBool nsClipboard::IsStringType(const nsCString& aMIMEType, const NSString** aPasteboardType)
544 if (aMIMEType.EqualsLiteral(kUnicodeMime) ||
545 aMIMEType.EqualsLiteral(kHTMLMime)) {
546 if (aMIMEType.EqualsLiteral(kUnicodeMime))
547 *aPasteboardType = NSStringPboardType;
549 *aPasteboardType = NSHTMLPboardType;