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 "CDataTransferer.h"
  27 #import "ThreadUtilities.h"
  28 #import "jni_util.h" 
  29 #import <Cocoa/Cocoa.h>
  30 #import <JavaNativeFoundation/JavaNativeFoundation.h>
  31 
  32 @interface CClipboard : NSObject { }
  33 @property NSInteger changeCount;
  34 @property jobject clipboardOwner;
  35 
  36 + (CClipboard*)sharedClipboard;
  37 - (void)declareTypes:(NSArray *)types withOwner:(jobject)owner jniEnv:(JNIEnv*)env;
  38 - (void)checkPasteboard:(id)sender;
  39 @end
  40 
  41 @implementation CClipboard
  42 @synthesize changeCount = _changeCount;
  43 @synthesize clipboardOwner = _clipboardOwner;
  44 
  45 // Clipboard creation is synchronized at the Java level
  46 + (CClipboard*)sharedClipboard {
  47     static CClipboard* sClipboard = nil;
  48     if (sClipboard == nil) {
  49         sClipboard = [[CClipboard alloc] init];
  50         [[NSNotificationCenter defaultCenter] addObserver:sClipboard selector: @selector(checkPasteboard:)
  51                                                      name: NSApplicationDidBecomeActiveNotification
  52                                                    object: nil];
  53     }
  54 
  55     return sClipboard;
  56 }
  57 
  58 - (id)init {
  59     if (self = [super init]) {
  60         self.changeCount = [[NSPasteboard generalPasteboard] changeCount];
  61     }
  62     return self;
  63 }
  64 
  65 - (void)declareTypes:(NSArray*)types withOwner:(jobject)owner jniEnv:(JNIEnv*)env {
  66     @synchronized(self) {
  67         if (owner != NULL) {
  68             if (self.clipboardOwner != NULL) {
  69                 JNFDeleteGlobalRef(env, self.clipboardOwner);
  70             }
  71             self.clipboardOwner = JNFNewGlobalRef(env, owner);
  72         }
  73     }
  74     [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
  75         self.changeCount = [[NSPasteboard generalPasteboard] declareTypes:types owner:self];
  76     }];
  77 }
  78 
  79 - (void)checkPasteboard:(id)sender {
  80 
  81     // This is called via NSApplicationDidBecomeActiveNotification.
  82     
  83     // If the change count on the general pasteboard is different than when we set it
  84     // someone else put data on the clipboard.  That means the current owner lost ownership.
  85     
  86     NSInteger newChangeCount = [[NSPasteboard generalPasteboard] changeCount];
  87 
  88     if (self.changeCount != newChangeCount) {
  89         self.changeCount = newChangeCount;
  90 
  91         // Notify that the content might be changed
  92         static JNF_CLASS_CACHE(jc_CClipboard, "sun/lwawt/macosx/CClipboard");
  93         static JNF_STATIC_MEMBER_CACHE(jm_contentChanged, jc_CClipboard, "notifyChanged", "()V");
  94         JNIEnv *env = [ThreadUtilities getJNIEnv];
  95         JNFCallStaticVoidMethod(env, jm_contentChanged);
  96 
  97         // If we have a Java pasteboard owner, tell it that it doesn't own the pasteboard anymore.
  98         static JNF_MEMBER_CACHE(jm_lostOwnership, jc_CClipboard, "notifyLostOwnership", "()V");
  99         @synchronized(self) {
 100             if (self.clipboardOwner) {
 101                 JNIEnv *env = [ThreadUtilities getJNIEnv];
 102                 JNFCallVoidMethod(env, self.clipboardOwner, jm_lostOwnership); // AWT_THREADING Safe (event)
 103                 JNFDeleteGlobalRef(env, self.clipboardOwner);
 104                 self.clipboardOwner = NULL;
 105             }
 106         }
 107     }
 108 }
 109 
 110 @end
 111 
 112 /*
 113  * Class:     sun_lwawt_macosx_CClipboard
 114  * Method:    declareTypes
 115  * Signature: ([JLsun/awt/datatransfer/SunClipboard;)V
 116 */
 117 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_declareTypes
 118 (JNIEnv *env, jobject inObject, jlongArray inTypes, jobject inJavaClip)
 119 {
 120 JNF_COCOA_ENTER(env);
 121 
 122     jint i;
 123     jint nElements = (*env)->GetArrayLength(env, inTypes);
 124     NSMutableArray *formatArray = [NSMutableArray arrayWithCapacity:nElements];
 125     jlong *elements = (*env)->GetPrimitiveArrayCritical(env, inTypes, NULL);
 126 
 127     for (i = 0; i < nElements; i++) {
 128         NSString *pbFormat = formatForIndex(elements[i]);
 129         if (pbFormat)
 130             [formatArray addObject:pbFormat];
 131     }
 132 
 133     (*env)->ReleasePrimitiveArrayCritical(env, inTypes, elements, JNI_ABORT);
 134     [[CClipboard sharedClipboard] declareTypes:formatArray withOwner:inJavaClip jniEnv:env];
 135 JNF_COCOA_EXIT(env);
 136 }
 137 
 138 /*
 139  * Class:     sun_lwawt_macosx_CClipboard
 140  * Method:    setData
 141  * Signature: ([BJ)V
 142 */
 143 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_setData
 144 (JNIEnv *env, jobject inObject, jbyteArray inBytes, jlong inFormat)
 145 {
 146     if (inBytes == NULL) {
 147         return;
 148     }
 149 
 150 JNF_COCOA_ENTER(env);
 151     jint nBytes = (*env)->GetArrayLength(env, inBytes);
 152     jbyte *rawBytes = (*env)->GetPrimitiveArrayCritical(env, inBytes, NULL);
 153     CHECK_NULL(rawBytes);
 154     NSData *bytesAsData = [NSData dataWithBytes:rawBytes length:nBytes];
 155     (*env)->ReleasePrimitiveArrayCritical(env, inBytes, rawBytes, JNI_ABORT);
 156     NSString *format = formatForIndex(inFormat);
 157     [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
 158         [[NSPasteboard generalPasteboard] setData:bytesAsData forType:format];
 159     }];
 160 JNF_COCOA_EXIT(env);
 161 }
 162 
 163 /*
 164  * Class:     sun_lwawt_macosx_CClipboard
 165  * Method:    getClipboardFormats
 166  * Signature: (J)[J
 167      */
 168 JNIEXPORT jlongArray JNICALL Java_sun_lwawt_macosx_CClipboard_getClipboardFormats
 169 (JNIEnv *env, jobject inObject)
 170 {
 171     jlongArray returnValue = NULL;
 172 JNF_COCOA_ENTER(env);
 173 
 174     __block NSArray* dataTypes;
 175     [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
 176         dataTypes = [[[NSPasteboard generalPasteboard] types] retain];
 177     }];
 178     [dataTypes autorelease];
 179     
 180     NSUInteger nFormats = [dataTypes count];
 181     NSUInteger knownFormats = 0;
 182     NSUInteger i;
 183 
 184     // There can be any number of formats on the general pasteboard.  Find out which ones
 185     // we know about (i.e., live in the flavormap.properties).
 186     for (i = 0; i < nFormats; i++) {
 187         NSString *format = (NSString *)[dataTypes objectAtIndex:i];
 188         if (indexForFormat(format) != -1)
 189             knownFormats++;
 190     }
 191 
 192     returnValue = (*env)->NewLongArray(env, knownFormats);
 193     if (returnValue == NULL) {
 194         return NULL;
 195     }
 196 
 197     if (knownFormats == 0) {
 198         return returnValue;
 199     }
 200 
 201     // Now go back and map the formats we found back to Java indexes.
 202     jboolean isCopy;
 203     jlong *lFormats = (*env)->GetLongArrayElements(env, returnValue, &isCopy);
 204     jlong *saveFormats = lFormats;
 205 
 206     for (i = 0; i < nFormats; i++) {
 207         NSString *format = (NSString *)[dataTypes objectAtIndex:i];
 208         jlong index = indexForFormat(format);
 209 
 210         if (index != -1) {
 211             *lFormats = index;
 212             lFormats++;
 213         }
 214     }
 215 
 216     (*env)->ReleaseLongArrayElements(env, returnValue, saveFormats, JNI_COMMIT);
 217 JNF_COCOA_EXIT(env);
 218     return returnValue;
 219 }
 220 
 221 /*
 222  * Class:     sun_lwawt_macosx_CClipboard
 223  * Method:    getClipboardData
 224  * Signature: (JJ)[B
 225      */
 226 JNIEXPORT jbyteArray JNICALL Java_sun_lwawt_macosx_CClipboard_getClipboardData
 227 (JNIEnv *env, jobject inObject, jlong format)
 228 {
 229     jbyteArray returnValue = NULL;
 230 
 231     // Note that this routine makes no attempt to interpret the data, since we're returning
 232     // a byte array back to Java.  CDataTransferer will do that if necessary.
 233 JNF_COCOA_ENTER(env);
 234 
 235     NSString *formatAsString = formatForIndex(format);
 236     __block NSData* clipData;
 237     [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
 238         clipData = [[[NSPasteboard generalPasteboard] dataForType:formatAsString] retain];
 239     }];
 240     
 241     if (clipData == NULL) {
 242         [JNFException raise:env as:"java/io/IOException" reason:"Font transform has NaN position"];
 243         return NULL;
 244     } else {
 245         [clipData autorelease];
 246     }
 247 
 248     NSUInteger dataSize = [clipData length];
 249     returnValue = (*env)->NewByteArray(env, dataSize);
 250     if (returnValue == NULL) {
 251         return NULL;
 252     }
 253 
 254     if (dataSize != 0) {
 255         const void *dataBuffer = [clipData bytes];
 256         (*env)->SetByteArrayRegion(env, returnValue, 0, dataSize, (jbyte *)dataBuffer);
 257     }
 258 
 259 JNF_COCOA_EXIT(env);
 260     return returnValue;
 261 }
 262 
 263 /*
 264  * Class:     sun_lwawt_macosx_CClipboard
 265  * Method:    checkPasteboard
 266  * Signature: ()V
 267  */
 268 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_checkPasteboard
 269 (JNIEnv *env, jobject inObject )
 270 {
 271 JNF_COCOA_ENTER(env);
 272 
 273     [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
 274         [[CClipboard sharedClipboard] checkPasteboard:nil];
 275     }];
 276         
 277 JNF_COCOA_EXIT(env);
 278 }
 279 
 280