1 /*
   2  * Copyright (c) 2011, 2014, 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 #import "CClipboard.h"
  27 #import "CDataTransferer.h"
  28 #import "ThreadUtilities.h"
  29 #import "jni_util.h" 
  30 #import <Cocoa/Cocoa.h>
  31 #import <JavaNativeFoundation/JavaNativeFoundation.h>
  32 
  33 static CClipboard *sClipboard = nil;
  34 
  35 //
  36 // CClipboardUpdate is used for mulitple calls to setData that happen before
  37 // the model and AppKit can get back in sync.
  38 //
  39 
  40 @interface CClipboardUpdate : NSObject {
  41     NSData *fData;
  42     NSString *fFormat;
  43 }
  44 
  45 - (id)initWithData:(NSData *)inData withFormat:(NSString *)inFormat;
  46 - (NSData *)data;
  47 - (NSString *)format;
  48 
  49 @end
  50 
  51 @implementation CClipboardUpdate
  52 
  53 - (id)initWithData:(NSData *)inData withFormat:(NSString *)inFormat
  54 {
  55     self = [super init];
  56 
  57     if (self != nil) {
  58         fData = [inData retain];
  59         fFormat = [inFormat retain];
  60     }
  61 
  62     return self;
  63 }
  64 
  65 - (void)dealloc
  66 {
  67     [fData release];
  68     fData = nil;
  69 
  70     [fFormat release];
  71     fFormat = nil;
  72 
  73     [super dealloc];
  74 }
  75 //- (void)finalize { [super finalize]; }
  76 
  77 - (NSData *)data {
  78     return fData;
  79 }
  80 
  81 - (NSString *)format {
  82     return fFormat;
  83 }
  84 @end
  85 
  86 @implementation CClipboard
  87 
  88 // Clipboard creation is synchronized at the Java level.
  89 + (CClipboard *) sharedClipboard
  90 {
  91     if (sClipboard == nil) {
  92         sClipboard = [[CClipboard alloc] init];
  93         [[NSNotificationCenter defaultCenter] addObserver:sClipboard selector: @selector(checkPasteboard:)
  94                                                      name: NSApplicationDidBecomeActiveNotification
  95                                                    object: nil];
  96     }
  97 
  98     return sClipboard;
  99 }
 100 
 101 - (id) init
 102 {
 103     self = [super init];
 104 
 105     if (self != nil) {
 106         fChangeCount = [[NSPasteboard generalPasteboard] changeCount];
 107     }
 108 
 109     return self;
 110 }
 111 
 112 - (void) javaDeclareTypes:(NSArray *)inTypes withOwner:(jobject)inClipboard jniEnv:(JNIEnv *)inEnv {
 113 
 114     @synchronized(self) {
 115         if (inClipboard != NULL) {
 116             if (fClipboardOwner != NULL) {
 117                 JNFDeleteGlobalRef(inEnv, fClipboardOwner);
 118             }
 119             fClipboardOwner = JNFNewGlobalRef(inEnv, inClipboard);
 120         }
 121     }
 122     [ThreadUtilities performOnMainThread:@selector(_nativeDeclareTypes:) on:self withObject:inTypes waitUntilDone:YES];
 123 }
 124 
 125 - (void) _nativeDeclareTypes:(NSArray *)inTypes {
 126     AWT_ASSERT_APPKIT_THREAD;
 127 
 128     fChangeCount = [[NSPasteboard generalPasteboard] declareTypes:inTypes owner:self];
 129 }
 130 
 131 
 132 - (NSArray *) javaGetTypes {
 133 
 134     NSMutableArray *args = [NSMutableArray arrayWithCapacity:1];
 135     [ThreadUtilities performOnMainThread:@selector(_nativeGetTypes:) on:self withObject:args waitUntilDone:YES];
 136     return [args lastObject];
 137 }
 138 
 139 - (void) _nativeGetTypes:(NSMutableArray *)args {
 140     AWT_ASSERT_APPKIT_THREAD;
 141 
 142     [args addObject:[[NSPasteboard generalPasteboard] types]];
 143 }
 144 
 145 - (void) javaSetData:(NSData *)inData forType:(NSString *) inFormat {
 146 
 147     CClipboardUpdate *newUpdate = [[CClipboardUpdate alloc] initWithData:inData withFormat:inFormat];
 148     [ThreadUtilities performOnMainThread:@selector(_nativeSetData:) on:self withObject:newUpdate waitUntilDone:YES];
 149     [newUpdate release];
 150 }
 151 
 152 - (void) _nativeSetData:(CClipboardUpdate *)newUpdate {
 153     AWT_ASSERT_APPKIT_THREAD;
 154 
 155     [[NSPasteboard generalPasteboard] setData:[newUpdate data] forType:[newUpdate format]];
 156 }
 157 
 158 - (NSData *) javaGetDataForType:(NSString *) inFormat {
 159 
 160     NSMutableArray *args = [NSMutableArray arrayWithObject:inFormat];
 161     [ThreadUtilities performOnMainThread:@selector(_nativeGetDataForType:) on:self withObject:args waitUntilDone:YES];
 162     return [args lastObject];
 163 }
 164 
 165 - (void) _nativeGetDataForType:(NSMutableArray *) args {
 166     AWT_ASSERT_APPKIT_THREAD;
 167 
 168     NSData *returnValue = [[NSPasteboard generalPasteboard] dataForType:[args objectAtIndex:0]];
 169 
 170     if (returnValue) [args replaceObjectAtIndex:0 withObject:returnValue];
 171     else [args removeLastObject];
 172 }
 173 
 174 
 175 
 176 - (void) checkPasteboard:(id)application {
 177     AWT_ASSERT_APPKIT_THREAD;
 178     
 179     // This is called via NSApplicationDidBecomeActiveNotification.
 180     
 181     // If the change count on the general pasteboard is different than when we set it
 182     // someone else put data on the clipboard.  That means the current owner lost ownership.
 183     NSInteger newChangeCount = [[NSPasteboard generalPasteboard] changeCount];
 184     
 185     if (fChangeCount != newChangeCount) {
 186         fChangeCount = newChangeCount;    
 187 
 188         // Notify that the content might be changed
 189         static JNF_CLASS_CACHE(jc_CClipboard, "sun/lwawt/macosx/CClipboard");
 190         static JNF_STATIC_MEMBER_CACHE(jm_contentChanged, jc_CClipboard, "notifyChanged", "()V");
 191         JNIEnv *env = [ThreadUtilities getJNIEnv];
 192         JNFCallStaticVoidMethod(env, jm_contentChanged);
 193 
 194         // If we have a Java pasteboard owner, tell it that it doesn't own the pasteboard anymore.
 195         static JNF_MEMBER_CACHE(jm_lostOwnership, jc_CClipboard, "notifyLostOwnership", "()V");
 196         @synchronized(self) {
 197             if (fClipboardOwner) {
 198                 JNIEnv *env = [ThreadUtilities getJNIEnv];
 199                 JNFCallVoidMethod(env, fClipboardOwner, jm_lostOwnership); // AWT_THREADING Safe (event)
 200                 JNFDeleteGlobalRef(env, fClipboardOwner);
 201                 fClipboardOwner = NULL;
 202             }
 203         }
 204     }
 205 }
 206 
 207 - (BOOL) checkPasteboardWithoutNotification:(id)application {
 208     AWT_ASSERT_APPKIT_THREAD;
 209     
 210     NSInteger newChangeCount = [[NSPasteboard generalPasteboard] changeCount];
 211     
 212     if (fChangeCount != newChangeCount) {
 213         fChangeCount = newChangeCount;    
 214         return YES;
 215     } else {
 216         return NO;
 217     }
 218 }
 219 
 220 @end
 221 
 222 /*
 223  * Class:     sun_lwawt_macosx_CClipboard
 224  * Method:    declareTypes
 225  * Signature: ([JLsun/awt/datatransfer/SunClipboard;)V
 226 */
 227 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_declareTypes
 228 (JNIEnv *env, jobject inObject, jlongArray inTypes, jobject inJavaClip)
 229 {
 230 JNF_COCOA_ENTER(env);
 231 
 232     jint i;
 233     jint nElements = (*env)->GetArrayLength(env, inTypes);
 234     NSMutableArray *formatArray = [NSMutableArray arrayWithCapacity:nElements];
 235     jlong *elements = (*env)->GetPrimitiveArrayCritical(env, inTypes, NULL);
 236 
 237     for (i = 0; i < nElements; i++) {
 238         NSString *pbFormat = formatForIndex(elements[i]);
 239         if (pbFormat)
 240             [formatArray addObject:pbFormat];
 241     }
 242 
 243     (*env)->ReleasePrimitiveArrayCritical(env, inTypes, elements, JNI_ABORT);
 244     [[CClipboard sharedClipboard] javaDeclareTypes:formatArray withOwner:inJavaClip jniEnv:env];
 245 JNF_COCOA_EXIT(env);
 246 }
 247 
 248 /*
 249  * Class:     sun_lwawt_macosx_CClipboard
 250  * Method:    setData
 251  * Signature: ([BJ)V
 252 */
 253 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_setData
 254 (JNIEnv *env, jobject inObject, jbyteArray inBytes, jlong inFormat)
 255 {
 256     if (inBytes == NULL) {
 257         return;
 258     }
 259 
 260 JNF_COCOA_ENTER(env);
 261     jint nBytes = (*env)->GetArrayLength(env, inBytes);
 262     jbyte *rawBytes = (*env)->GetPrimitiveArrayCritical(env, inBytes, NULL);
 263     CHECK_NULL(rawBytes);
 264     NSData *bytesAsData = [NSData dataWithBytes:rawBytes length:nBytes];
 265     (*env)->ReleasePrimitiveArrayCritical(env, inBytes, rawBytes, JNI_ABORT);
 266     NSString *format = formatForIndex(inFormat);
 267     [[CClipboard sharedClipboard] javaSetData:bytesAsData forType:format];
 268 JNF_COCOA_EXIT(env);
 269 }
 270 
 271 /*
 272  * Class:     sun_lwawt_macosx_CClipboard
 273  * Method:    getClipboardFormats
 274  * Signature: (J)[J
 275      */
 276 JNIEXPORT jlongArray JNICALL Java_sun_lwawt_macosx_CClipboard_getClipboardFormats
 277 (JNIEnv *env, jobject inObject)
 278 {
 279     jlongArray returnValue = NULL;
 280 JNF_COCOA_ENTER(env);
 281 
 282     NSArray *dataTypes = [[CClipboard sharedClipboard] javaGetTypes];
 283     NSUInteger nFormats = [dataTypes count];
 284     NSUInteger knownFormats = 0;
 285     NSUInteger i;
 286 
 287     // There can be any number of formats on the general pasteboard.  Find out which ones
 288     // we know about (i.e., live in the flavormap.properties).
 289     for (i = 0; i < nFormats; i++) {
 290         NSString *format = (NSString *)[dataTypes objectAtIndex:i];
 291         if (indexForFormat(format) != -1)
 292             knownFormats++;
 293     }
 294 
 295     returnValue = (*env)->NewLongArray(env, knownFormats);
 296     if (returnValue == NULL) {
 297         return NULL;
 298     }
 299 
 300     if (knownFormats == 0) {
 301         return returnValue;
 302     }
 303 
 304     // Now go back and map the formats we found back to Java indexes.
 305     jboolean isCopy;
 306     jlong *lFormats = (*env)->GetLongArrayElements(env, returnValue, &isCopy);
 307     jlong *saveFormats = lFormats;
 308 
 309     for (i = 0; i < nFormats; i++) {
 310         NSString *format = (NSString *)[dataTypes objectAtIndex:i];
 311         jlong index = indexForFormat(format);
 312 
 313         if (index != -1) {
 314             *lFormats = index;
 315             lFormats++;
 316         }
 317     }
 318 
 319     (*env)->ReleaseLongArrayElements(env, returnValue, saveFormats, JNI_COMMIT);
 320 JNF_COCOA_EXIT(env);
 321     return returnValue;
 322 }
 323 
 324 /*
 325  * Class:     sun_lwawt_macosx_CClipboard
 326  * Method:    getClipboardData
 327  * Signature: (JJ)[B
 328      */
 329 JNIEXPORT jbyteArray JNICALL Java_sun_lwawt_macosx_CClipboard_getClipboardData
 330 (JNIEnv *env, jobject inObject, jlong format)
 331 {
 332     jbyteArray returnValue = NULL;
 333 
 334     // Note that this routine makes no attempt to interpret the data, since we're returning
 335     // a byte array back to Java.  CDataTransferer will do that if necessary.
 336 JNF_COCOA_ENTER(env);
 337 
 338     NSString *formatAsString = formatForIndex(format);
 339     NSData *clipData = [[CClipboard sharedClipboard] javaGetDataForType:formatAsString];
 340 
 341     if (clipData == NULL) {
 342         [JNFException raise:env as:"java/io/IOException" reason:"Font transform has NaN position"];
 343         return NULL;
 344     }
 345 
 346     NSUInteger dataSize = [clipData length];
 347     returnValue = (*env)->NewByteArray(env, dataSize);
 348     if (returnValue == NULL) {
 349         return NULL;
 350     }
 351 
 352     if (dataSize != 0) {
 353         const void *dataBuffer = [clipData bytes];
 354         (*env)->SetByteArrayRegion(env, returnValue, 0, dataSize, (jbyte *)dataBuffer);
 355     }
 356 
 357 JNF_COCOA_EXIT(env);
 358     return returnValue;
 359 }
 360 
 361 /*
 362  * Class:     sun_lwawt_macosx_CClipboard
 363  * Method:    checkPasteboard
 364  * Signature: ()V
 365  */
 366 JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CClipboard_checkPasteboardWithoutNotification
 367 (JNIEnv *env, jobject inObject)
 368 {
 369     __block BOOL ret = NO;
 370     JNF_COCOA_ENTER(env);
 371     [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
 372         ret = [[CClipboard sharedClipboard] checkPasteboardWithoutNotification:nil];
 373     }];
 374      
 375     JNF_COCOA_EXIT(env);
 376     return ret;
 377 }
 378 
 379