1 /*
   2  * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #include "CClipboard.h"
  27 #include "CDataTransferer.h"
  28 #import <Cocoa/Cocoa.h>
  29 #import <JavaNativeFoundation/JavaNativeFoundation.h>
  30 
  31 #include "ThreadUtilities.h"
  32 
  33 
  34 static CClipboard *sClipboard = nil;
  35 
  36 //
  37 // CClipboardUpdate is used for mulitple calls to setData that happen before
  38 // the model and AppKit can get back in sync.
  39 //
  40 
  41 @interface CClipboardUpdate : NSObject {
  42     NSData *fData;
  43     NSString *fFormat;
  44 }
  45 
  46 - (id)initWithData:(NSData *)inData withFormat:(NSString *)inFormat;
  47 - (NSData *)data;
  48 - (NSString *)format;
  49 
  50 @end
  51 
  52 @implementation CClipboardUpdate
  53 
  54 - (id)initWithData:(NSData *)inData withFormat:(NSString *)inFormat
  55 {
  56     self = [super init];
  57 
  58     if (self != nil) {
  59         fData = [inData retain];
  60         fFormat = [inFormat retain];
  61     }
  62 
  63     return self;
  64 }
  65 
  66 - (void)dealloc
  67 {
  68     [fData release];
  69     fData = nil;
  70 
  71     [fFormat release];
  72     fFormat = nil;
  73 
  74     [super dealloc];
  75 }
  76 //- (void)finalize { [super finalize]; }
  77 
  78 - (NSData *)data {
  79     return fData;
  80 }
  81 
  82 - (NSString *)format {
  83     return fFormat;
  84 }
  85 @end
  86 
  87 @implementation CClipboard
  88 
  89 // Clipboard creation is synchronized at the Java level.
  90 + (CClipboard *) sharedClipboard
  91 {
  92     if (sClipboard == nil) {
  93         sClipboard = [[CClipboard alloc] init];
  94         [[NSNotificationCenter defaultCenter] addObserver:sClipboard selector: @selector(checkPasteboard:) name: NSApplicationDidBecomeActiveNotification object: nil];
  95     }
  96 
  97     return sClipboard;
  98 }
  99 
 100 - (id) init
 101 {
 102     self = [super init];
 103 
 104     if (self != nil) {
 105         fChangeCount = [[NSPasteboard generalPasteboard] changeCount];
 106     }
 107 
 108     return self;
 109 }
 110 
 111 - (void) javaDeclareTypes:(NSArray *)inTypes withOwner:(jobject)inClipboard jniEnv:(JNIEnv *)inEnv {
 112 
 113     //NSLog(@"CClipboard javaDeclareTypes %@ withOwner", inTypes);
 114 
 115     @synchronized(self) {
 116         if (inClipboard != NULL) {
 117             if (fClipboardOwner != NULL) {
 118                 JNFDeleteGlobalRef(inEnv, fClipboardOwner);
 119             }
 120             fClipboardOwner = JNFNewGlobalRef(inEnv, inClipboard);
 121         }
 122     }
 123     [ThreadUtilities performOnMainThread:@selector(_nativeDeclareTypes:) onObject:self withObject:inTypes waitUntilDone:YES awtMode:YES];
 124 }
 125 
 126 - (void) _nativeDeclareTypes:(NSArray *)inTypes {
 127     AWT_ASSERT_APPKIT_THREAD;
 128 
 129     //NSLog(@"CClipboard _nativeDeclareTypes %@ withOwner", inTypes);
 130 
 131     fChangeCount = [[NSPasteboard generalPasteboard] declareTypes:inTypes owner:self];
 132 }
 133 
 134 
 135 - (NSArray *) javaGetTypes {
 136 
 137     NSMutableArray *args = [NSMutableArray arrayWithCapacity:1];
 138     [ThreadUtilities performOnMainThread:@selector(_nativeGetTypes:) onObject:self withObject:args waitUntilDone:YES awtMode:YES];
 139 
 140     //NSLog(@"CClipboard getTypes returns %@", [args lastObject]);
 141     return [args lastObject];
 142 }
 143 
 144 - (void) _nativeGetTypes:(NSMutableArray *)args {
 145     AWT_ASSERT_APPKIT_THREAD;
 146 
 147     [args addObject:[[NSPasteboard generalPasteboard] types]];
 148 
 149     //NSLog(@"CClipboard getTypes returns %@", [args lastObject]);
 150 }
 151 
 152 - (void) javaSetData:(NSData *)inData forType:(NSString *) inFormat {
 153 
 154     CClipboardUpdate *newUpdate = [[CClipboardUpdate alloc] initWithData:inData withFormat:inFormat];
 155     [ThreadUtilities performOnMainThread:@selector(_nativeSetData:) onObject:self withObject:newUpdate waitUntilDone:YES awtMode:YES];
 156     [newUpdate release];
 157 
 158     //NSLog(@"CClipboard javaSetData forType %@", inFormat);
 159 }
 160 
 161 - (void) _nativeSetData:(CClipboardUpdate *)newUpdate {
 162     AWT_ASSERT_APPKIT_THREAD;
 163 
 164     [[NSPasteboard generalPasteboard] setData:[newUpdate data] forType:[newUpdate format]];
 165 
 166     //NSLog(@"CClipboard _nativeSetData setData %@", [newUpdate data]);
 167     //NSLog(@"CClipboard _nativeSetData forType %@", [newUpdate format]);
 168 }
 169 
 170 - (NSData *) javaGetDataForType:(NSString *) inFormat {
 171 
 172     NSMutableArray *args = [NSMutableArray arrayWithObject:inFormat];
 173     [ThreadUtilities performOnMainThread:@selector(_nativeGetDataForType:) onObject:self withObject:args waitUntilDone:YES awtMode:YES];
 174 
 175     //NSLog(@"CClipboard javaGetDataForType %@ returns an NSData", inFormat);
 176     return [args lastObject];
 177 }
 178 
 179 - (void) _nativeGetDataForType:(NSMutableArray *) args {
 180     AWT_ASSERT_APPKIT_THREAD;
 181 
 182     NSData *returnValue = [[NSPasteboard generalPasteboard] dataForType:[args objectAtIndex:0]];
 183 
 184     if (returnValue) [args replaceObjectAtIndex:0 withObject:returnValue];
 185     else [args removeLastObject];
 186 
 187     //NSLog(@"CClipboard _nativeGetDataForType");
 188 }
 189 
 190 - (void) checkPasteboard:(id)application {
 191     AWT_ASSERT_APPKIT_THREAD;
 192     
 193     //NSLog(@"CClipboard checkPasteboard oldCount %d newCount %d newTypes %@", fChangeCount, [[NSPasteboard generalPasteboard] changeCount], [[NSPasteboard generalPasteboard] types]);
 194     
 195     // This is called via NSApplicationDidBecomeActiveNotification.
 196     
 197     // If the change count on the general pasteboard is different than when we set it
 198     // someone else put data on the clipboard.  That means the current owner lost ownership.
 199     NSInteger newChangeCount = [[NSPasteboard generalPasteboard] changeCount];
 200     
 201     if (fChangeCount != newChangeCount) {
 202         fChangeCount = newChangeCount;
 203         
 204         [self pasteboardChangedOwner:[NSPasteboard generalPasteboard]];
 205     }
 206 }
 207 
 208 - (void)pasteboardChangedOwner:(NSPasteboard *)sender; {
 209     AWT_ASSERT_APPKIT_THREAD;
 210 
 211     static JNF_CLASS_CACHE(jc_CClipboard, "sun/lwawt/macosx/CClipboard");
 212     static JNF_MEMBER_CACHE(jm_lostOwnership, jc_CClipboard, "lostSelectionOwnershipImpl", "()V");
 213 
 214     //NSLog(@"CClipboard pasteboardChangedOwner");
 215 
 216     // If we have a Java pasteboard owner, tell it that it doesn't own the pasteboard anymore.
 217     @synchronized(self) {
 218         if (fClipboardOwner) {
 219             JNIEnv *env = [ThreadUtilities getJNIEnv];
 220             JNFCallVoidMethod(env, fClipboardOwner, jm_lostOwnership); // AWT_THREADING Safe (event)
 221             JNFDeleteGlobalRef(env, fClipboardOwner);
 222             fClipboardOwner = NULL;
 223         }
 224     }
 225 }
 226 
 227 @end
 228 
 229 /*
 230  * Class:     sun_lwawt_macosx_CClipboard
 231  * Method:    declareTypes
 232  * Signature: ([JLsun/awt/datatransfer/SunClipboard;)V
 233 */
 234 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_declareTypes
 235 (JNIEnv *env, jobject inObject, jlongArray inTypes, jobject inJavaClip)
 236 {
 237 JNF_COCOA_ENTER(env);
 238 
 239     jint i;
 240     jint nElements = (*env)->GetArrayLength(env, inTypes);
 241     NSMutableArray *formatArray = [NSMutableArray arrayWithCapacity:nElements];
 242     jlong *elements = (*env)->GetPrimitiveArrayCritical(env, inTypes, NULL);
 243 
 244     for (i = 0; i < nElements; i++) {
 245         NSString *pbFormat = formatForIndex(elements[i]);
 246         if (pbFormat)
 247             [formatArray addObject:pbFormat];
 248     }
 249 
 250     (*env)->ReleasePrimitiveArrayCritical(env, inTypes, elements, JNI_ABORT);
 251     [[CClipboard sharedClipboard] javaDeclareTypes:formatArray withOwner:inJavaClip jniEnv:env];
 252 JNF_COCOA_EXIT(env);
 253 }
 254 
 255 /*
 256  * Class:     sun_lwawt_macosx_CClipboard
 257  * Method:    setData
 258  * Signature: ([BJ)V
 259 */
 260 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_setData
 261 (JNIEnv *env, jobject inObject, jbyteArray inBytes, jlong inFormat)
 262 {
 263     if (inBytes == NULL) {
 264         return;
 265     }
 266 
 267 JNF_COCOA_ENTER(env);
 268 
 269     //NSLog(@"Java_sun_lwawt_macosx_CClipboard_setData");
 270 
 271     jint nBytes = (*env)->GetArrayLength(env, inBytes);
 272     jbyte *rawBytes = (*env)->GetPrimitiveArrayCritical(env, inBytes, NULL);
 273     NSData *bytesAsData = [NSData dataWithBytes:rawBytes length:nBytes];
 274     (*env)->ReleasePrimitiveArrayCritical(env, inBytes, rawBytes, JNI_ABORT);
 275     NSString *format = formatForIndex(inFormat);
 276     [[CClipboard sharedClipboard] javaSetData:bytesAsData forType:format];
 277 JNF_COCOA_EXIT(env);
 278 }
 279 
 280 /*
 281  * Class:     sun_lwawt_macosx_CClipboard
 282  * Method:    getClipboardFormats
 283  * Signature: (J)[J
 284      */
 285 JNIEXPORT jlongArray JNICALL Java_sun_lwawt_macosx_CClipboard_getClipboardFormats
 286 (JNIEnv *env, jobject inObject)
 287 {
 288     jlongArray returnValue = NULL;
 289 JNF_COCOA_ENTER(env);
 290 
 291     //NSLog(@"Java_sun_lwawt_macosx_CClipboard_getClipboardFormats");
 292 
 293     NSArray *dataTypes = [[CClipboard sharedClipboard] javaGetTypes];
 294     NSUInteger nFormats = [dataTypes count];
 295     NSUInteger knownFormats = 0;
 296     NSUInteger i;
 297 
 298     // There can be any number of formats on the general pasteboard.  Find out which ones
 299     // we know about (i.e., live in the flavormap.properties).
 300     for (i = 0; i < nFormats; i++) {
 301         NSString *format = (NSString *)[dataTypes objectAtIndex:i];
 302         if (indexForFormat(format) != -1)
 303             knownFormats++;
 304     }
 305 
 306     returnValue = (*env)->NewLongArray(env, knownFormats);
 307     if (returnValue == NULL) {
 308         return NULL;
 309     }
 310 
 311     if (knownFormats == 0) {
 312         return returnValue;
 313     }
 314 
 315     // Now go back and map the formats we found back to Java indexes.
 316     jboolean isCopy;
 317     jlong *lFormats = (*env)->GetLongArrayElements(env, returnValue, &isCopy);
 318     jlong *saveFormats = lFormats;
 319 
 320     for (i = 0; i < nFormats; i++) {
 321         NSString *format = (NSString *)[dataTypes objectAtIndex:i];
 322         jlong index = indexForFormat(format);
 323 
 324         if (index != -1) {
 325             *lFormats = index;
 326             lFormats++;
 327         }
 328     }
 329 
 330     (*env)->ReleaseLongArrayElements(env, returnValue, saveFormats, JNI_COMMIT);
 331 JNF_COCOA_EXIT(env);
 332     return returnValue;
 333 }
 334 
 335 /*
 336  * Class:     sun_lwawt_macosx_CClipboard
 337  * Method:    getClipboardData
 338  * Signature: (JJ)[B
 339      */
 340 JNIEXPORT jbyteArray JNICALL Java_sun_lwawt_macosx_CClipboard_getClipboardData
 341 (JNIEnv *env, jobject inObject, jlong format)
 342 {
 343     jbyteArray returnValue = NULL;
 344 
 345     // Note that this routine makes no attempt to interpret the data, since we're returning
 346     // a byte array back to Java.  CDataTransferer will do that if necessary.
 347 JNF_COCOA_ENTER(env);
 348 
 349     //NSLog(@"Java_sun_lwawt_macosx_CClipboard_getClipboardData");
 350 
 351     NSString *formatAsString = formatForIndex(format);
 352     NSData *clipData = [[CClipboard sharedClipboard] javaGetDataForType:formatAsString];
 353 
 354     if (clipData == NULL) {
 355         [JNFException raise:env as:"java/io/IOException" reason:"Font transform has NaN position"];
 356         return NULL;
 357     }
 358 
 359     NSUInteger dataSize = [clipData length];
 360     returnValue = (*env)->NewByteArray(env, dataSize);
 361     if (returnValue == NULL) {
 362         return NULL;
 363     }
 364 
 365     if (dataSize != 0) {
 366         const void *dataBuffer = [clipData bytes];
 367         (*env)->SetByteArrayRegion(env, returnValue, 0, dataSize, (jbyte *)dataBuffer);
 368     }
 369 
 370 JNF_COCOA_EXIT(env);
 371     return returnValue;
 372 }
 373 
 374 /*
 375  * Class:     sun_lwawt_macosx_CClipboard
 376  * Method:    checkPasteboard
 377  * Signature: ()V
 378  */
 379 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_checkPasteboard
 380 (JNIEnv *env, jobject inObject )
 381 {
 382     JNF_COCOA_ENTER(env);
 383 
 384     [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
 385         [[CClipboard sharedClipboard] checkPasteboard:nil];
 386     }];
 387         
 388     JNF_COCOA_EXIT(env);
 389 }
 390 
 391