SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
HexFiend/HFPasteboardOwner.m
1 //
2 // HFPasteboardOwner.m
3 // HexFiend_2
4 //
5 // Copyright 2008 ridiculous_fish. All rights reserved.
6 //
7
8 #import <HexFiend/HFPasteboardOwner.h>
9 #import <HexFiend/HFController.h>
10 #import <HexFiend/HFByteArray.h>
11 #import <objc/message.h>
12
13 NSString *const HFPrivateByteArrayPboardType = @"HFPrivateByteArrayPboardType";
14
15 @implementation HFPasteboardOwner
16
17 + (void)initialize {
18 if (self == [HFPasteboardOwner class]) {
19 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(prepareCommonPasteboardsForChangeInFileNotification:) name:HFPrepareForChangeInFileNotification object:nil];
20 }
21 }
22
23 - (instancetype)initWithPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types {
24 REQUIRE_NOT_NULL(pboard);
25 REQUIRE_NOT_NULL(array);
26 REQUIRE_NOT_NULL(types);
27 self = [super init];
28 byteArray = [array retain];
29 pasteboard = pboard;
30 [pasteboard declareTypes:types owner:self];
31
32 // get notified when we're about to write a file, so that if they're overwriting a file backing part of our byte array, we can properly clear or preserve our pasteboard
33 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeInFileNotification:) name:HFPrepareForChangeInFileNotification object:nil];
34
35 return self;
36 }
37 + (id)ownPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types {
38 return [[[self alloc] initWithPasteboard:pboard forByteArray:array withTypes:types] autorelease];
39 }
40
41 - (void)tearDownPasteboardReferenceIfExists {
42 if (pasteboard) {
43 pasteboard = nil;
44 [[NSNotificationCenter defaultCenter] removeObserver:self name:HFPrepareForChangeInFileNotification object:nil];
45 }
46 if (retainedSelfOnBehalfOfPboard) {
47 [self release];
48 retainedSelfOnBehalfOfPboard = NO;
49 }
50 }
51
52
53 + (HFByteArray *)_unpackByteArrayFromDictionary:(NSDictionary *)byteArrayDictionary {
54 HFByteArray *result = nil;
55 if (byteArrayDictionary) {
56 NSString *uuid = byteArrayDictionary[@"HFUUID"];
57 if ([uuid isEqual:[self uuid]]) {
58 result = (HFByteArray *)[byteArrayDictionary[@"HFByteArray"] unsignedLongValue];
59 }
60 }
61 return result;
62 }
63
64 + (HFByteArray *)unpackByteArrayFromPasteboard:(NSPasteboard *)pasteboard {
65 REQUIRE_NOT_NULL(pasteboard);
66 HFByteArray *result = [self _unpackByteArrayFromDictionary:[pasteboard propertyListForType:HFPrivateByteArrayPboardType]];
67 return result;
68 }
69
70 /* Try to fix up commonly named pasteboards when a file is about to be saved */
71 + (void)prepareCommonPasteboardsForChangeInFileNotification:(NSNotification *)notification {
72 const BOOL *cancellationPointer = [[notification userInfo][HFChangeInFileShouldCancelKey] pointerValue];
73 if (*cancellationPointer) return; //don't do anything if someone requested cancellation
74
75 NSDictionary *userInfo = [notification userInfo];
76 NSArray *changedRanges = userInfo[HFChangeInFileModifiedRangesKey];
77 HFFileReference *fileReference = [notification object];
78 NSMutableDictionary *hint = userInfo[HFChangeInFileHintKey];
79
80 NSString * const names[] = {NSGeneralPboard, NSFindPboard, NSDragPboard};
81 NSUInteger i;
82 for (i=0; i < sizeof names / sizeof *names; i++) {
83 NSPasteboard *pboard = [NSPasteboard pasteboardWithName:names[i]];
84 HFByteArray *byteArray = [self unpackByteArrayFromPasteboard:pboard];
85 if (byteArray && ! [byteArray clearDependenciesOnRanges:changedRanges inFile:fileReference hint:hint]) {
86 /* This pasteboard no longer works */
87 [pboard declareTypes:@[] owner:nil];
88 }
89 }
90 }
91
92 - (void)changeInFileNotification:(NSNotification *)notification {
93 HFASSERT(pasteboard != nil);
94 HFASSERT(byteArray != nil);
95 NSDictionary *userInfo = [notification userInfo];
96 const BOOL *cancellationPointer = [userInfo[HFChangeInFileShouldCancelKey] pointerValue];
97 if (*cancellationPointer) return; //don't do anything if someone requested cancellation
98 NSMutableDictionary *hint = userInfo[HFChangeInFileHintKey];
99
100 NSArray *changedRanges = [notification userInfo][HFChangeInFileModifiedRangesKey];
101 HFFileReference *fileReference = [notification object];
102 if (! [byteArray clearDependenciesOnRanges:changedRanges inFile:fileReference hint:hint]) {
103 /* We can't do it */
104 [self tearDownPasteboardReferenceIfExists];
105 }
106 }
107
108 - (void)dealloc {
109 [self tearDownPasteboardReferenceIfExists];
110 [byteArray release];
111 [super dealloc];
112 }
113
114 - (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker {
115 USE(length);
116 USE(pboard);
117 USE(type);
118 USE(tracker);
119 UNIMPLEMENTED_VOID();
120 }
121
122 - (void)backgroundMoveDataToPasteboard:(NSString *)type {
123 @autoreleasepool {
124 [self writeDataInBackgroundToPasteboard:pasteboard ofLength:dataAmountToCopy forType:type trackingProgress:nil];
125 [self performSelectorOnMainThread:@selector(backgroundMoveDataFinished:) withObject:nil waitUntilDone:NO];
126 }
127 }
128
129 - (void)backgroundMoveDataFinished:unused {
130 USE(unused);
131 HFASSERT(backgroundCopyOperationFinished == NO);
132 backgroundCopyOperationFinished = YES;
133 if (! didStartModalSessionForBackgroundCopyOperation) {
134 /* We haven't started the modal session, so make sure it never happens */
135 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(beginModalSessionForBackgroundCopyOperation:) object:nil];
136 CFRunLoopWakeUp(CFRunLoopGetCurrent());
137 }
138 else {
139 /* We have started the modal session, so end it. */
140 [NSApp stopModalWithCode:0];
141 //stopModal: won't trigger unless we post a do-nothing event
142 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:NULL subtype:0 data1:0 data2:0];
143 [NSApp postEvent:event atStart:NO];
144 }
145 }
146
147 - (void)beginModalSessionForBackgroundCopyOperation:(id)unused {
148 USE(unused);
149 HFASSERT(backgroundCopyOperationFinished == NO);
150 HFASSERT(didStartModalSessionForBackgroundCopyOperation == NO);
151 didStartModalSessionForBackgroundCopyOperation = YES;
152 }
153
154 - (BOOL)moveDataWithProgressReportingToPasteboard:(NSPasteboard *)pboard forType:(NSString *)type {
155 // The -[NSRunLoop runMode:beforeDate:] call in the middle of this function can cause it to be
156 // called reentrantly, which was previously causing leaks and use-after-free crashes. For
157 // some reason this happens basically always when copying lots of data into VMware Fusion.
158 // I'm not even sure what the ideal behavior would be here, but am fairly certain that this
159 // is the best that can be done without rewriting a portion of the background copying code.
160 // TODO: Figure out what the ideal behavior should be here.
161
162 HFASSERT(pboard == pasteboard);
163 [self retain]; //resolving the pasteboard may release us, which deallocates us, which deallocates our tracker...make sure we survive through this function
164 /* Give the user a chance to request a smaller amount if it's really big */
165 unsigned long long availableAmount = [byteArray length];
166 unsigned long long amountToCopy = [self amountToCopyForDataLength:availableAmount stringLength:[self stringLengthForDataLength:availableAmount]];
167 if (amountToCopy > 0) {
168
169 backgroundCopyOperationFinished = NO;
170 didStartModalSessionForBackgroundCopyOperation = NO;
171 dataAmountToCopy = amountToCopy;
172 [NSThread detachNewThreadSelector:@selector(backgroundMoveDataToPasteboard:) toTarget:self withObject:type];
173 [self performSelector:@selector(beginModalSessionForBackgroundCopyOperation:) withObject:nil afterDelay:1.0 inModes:@[NSModalPanelRunLoopMode]];
174 while (! backgroundCopyOperationFinished) {
175 [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate distantFuture]];
176 }
177 }
178 [self release];
179 return YES;
180 }
181
182 - (void)pasteboardChangedOwner:(NSPasteboard *)pboard {
183 HFASSERT(pasteboard == pboard);
184 [self tearDownPasteboardReferenceIfExists];
185 }
186
187 - (HFByteArray *)byteArray {
188 return byteArray;
189 }
190
191 - (void)pasteboard:(NSPasteboard *)pboard provideDataForType:(NSString *)type {
192 if (! pasteboard) {
193 /* Don't do anything, because we've torn down our pasteboard */
194 return;
195 }
196 if ([type isEqualToString:HFPrivateByteArrayPboardType]) {
197 if (! retainedSelfOnBehalfOfPboard) {
198 retainedSelfOnBehalfOfPboard = YES;
199 CFRetain(self);
200 }
201 NSDictionary *dict = @{@"HFByteArray": @((unsigned long)byteArray),
202 @"HFUUID": [[self class] uuid]};
203 [pboard setPropertyList:dict forType:type];
204 }
205 else {
206 if (! [self moveDataWithProgressReportingToPasteboard:pboard forType:type]) {
207 [pboard setData:[NSData data] forType:type];
208 }
209 }
210 }
211
212 - (void)setBytesPerLine:(NSUInteger)val { bytesPerLine = val; }
213 - (NSUInteger)bytesPerLine { return bytesPerLine; }
214
215 + (NSString *)uuid {
216 static NSString *uuid;
217 if (! uuid) {
218 CFUUIDRef uuidRef = CFUUIDCreate(NULL);
219 uuid = (NSString *)CFUUIDCreateString(NULL, uuidRef);
220 CFRelease(uuidRef);
221 }
222 return uuid;
223 }
224
225 - (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength { USE(dataLength); UNIMPLEMENTED(); }
226
227 - (unsigned long long)amountToCopyForDataLength:(unsigned long long)numBytes stringLength:(unsigned long long)stringLength {
228 unsigned long long dataLengthResult, stringLengthResult;
229 NSInteger alertReturn = NSIntegerMax;
230 const unsigned long long copyOption1 = MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT;
231 const unsigned long long copyOption2 = MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT;
232 NSString *option1String = HFDescribeByteCount(copyOption1);
233 NSString *option2String = HFDescribeByteCount(copyOption2);
234 NSString* dataSizeDescription = HFDescribeByteCount(stringLength);
235 if (stringLength >= MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT) {
236 NSString *option1 = [@"Copy " stringByAppendingString:option1String];
237 NSString *option2 = [@"Copy " stringByAppendingString:option2String];
238 alertReturn = NSRunAlertPanel(@"Large Clipboard", @"The copied data would occupy %@ if written to the clipboard. This is larger than the system clipboard supports. Do you want to copy only part of the data?", @"Cancel", option1, option2, dataSizeDescription);
239 switch (alertReturn) {
240 case NSAlertDefaultReturn:
241 default:
242 stringLengthResult = 0;
243 break;
244 case NSAlertAlternateReturn:
245 stringLengthResult = copyOption1;
246 break;
247 case NSAlertOtherReturn:
248 stringLengthResult = copyOption2;
249 break;
250 }
251
252 }
253 else if (stringLength >= MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT) {
254 NSString *option1 = [@"Copy " stringByAppendingString:HFDescribeByteCount(stringLength)];
255 NSString *option2 = [@"Copy " stringByAppendingString:HFDescribeByteCount(copyOption2)];
256 alertReturn = NSRunAlertPanel(@"Large Clipboard", @"The copied data would occupy %@ if written to the clipboard. Performing this copy may take a long time. Do you want to copy only part of the data?", @"Cancel", option1, option2, dataSizeDescription);
257 switch (alertReturn) {
258 case NSAlertDefaultReturn:
259 default:
260 stringLengthResult = 0;
261 break;
262 case NSAlertAlternateReturn:
263 stringLengthResult = stringLength;
264 break;
265 case NSAlertOtherReturn:
266 stringLengthResult = copyOption2;
267 break;
268 }
269 }
270 else {
271 /* Small enough to copy it all */
272 stringLengthResult = stringLength;
273 }
274
275 /* Convert from string length to data length */
276 if (stringLengthResult == stringLength) {
277 dataLengthResult = numBytes;
278 }
279 else {
280 unsigned long long divisor = stringLength / numBytes;
281 dataLengthResult = stringLengthResult / divisor;
282 }
283
284 return dataLengthResult;
285 }
286
287 @end
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.