1283 lines
34 KiB
Plaintext
1283 lines
34 KiB
Plaintext
/*
|
|
* clip_macosx64.mm - Clipboard handling, MacOS X (Pasteboard Manager) implementation
|
|
*
|
|
* (C) 2012 Jean-Pierre Stierlin
|
|
* (C) 2012 Alexei Svitkine
|
|
* (C) 2012 Charles Srstka
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "sysdeps.h"
|
|
#define _UINT64
|
|
#import <Cocoa/Cocoa.h>
|
|
#include <ApplicationServices/ApplicationServices.h>
|
|
|
|
#include "clip.h"
|
|
#include "main.h"
|
|
#include "cpu_emulation.h"
|
|
#include "emul_op.h"
|
|
#include "autorelease.h"
|
|
#include "pict.h"
|
|
|
|
#define DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#ifndef FOURCC
|
|
#define FOURCC(a,b,c,d) (((uint32)(a) << 24) | ((uint32)(b) << 16) | ((uint32)(c) << 8) | (uint32)(d))
|
|
#endif
|
|
|
|
#define TYPE_PICT FOURCC('P','I','C','T')
|
|
#define TYPE_TEXT FOURCC('T','E','X','T')
|
|
#define TYPE_STYL FOURCC('s','t','y','l')
|
|
#define TYPE_UTXT FOURCC('u','t','x','t')
|
|
#define TYPE_UT16 FOURCC('u','t','1','6')
|
|
#define TYPE_USTL FOURCC('u','s','t','l')
|
|
#define TYPE_MOOV FOURCC('m','o','o','v')
|
|
#define TYPE_SND FOURCC('s','n','d',' ')
|
|
#define TYPE_ICNS FOURCC('i','c','n','s')
|
|
|
|
static NSPasteboard *g_pboard;
|
|
static NSInteger g_pb_change_count = 0;
|
|
|
|
// Flag for PutScrap(): the data was put by GetScrap(), don't bounce it back to the MacOS X side
|
|
static bool we_put_this_data = false;
|
|
|
|
static bool should_clear = false;
|
|
|
|
static NSMutableDictionary *g_macScrap;
|
|
|
|
// font face types
|
|
|
|
enum {
|
|
FONT_FACE_PLAIN = 0,
|
|
FONT_FACE_BOLD = 1,
|
|
FONT_FACE_ITALIC = 2,
|
|
FONT_FACE_UNDERLINE = 4,
|
|
FONT_FACE_OUTLINE = 8,
|
|
FONT_FACE_SHADOW = 16,
|
|
FONT_FACE_CONDENSED = 32,
|
|
FONT_FACE_EXTENDED = 64
|
|
};
|
|
|
|
// Script Manager constants
|
|
|
|
#define smRoman 0
|
|
#define smMacSysScript 18
|
|
#define smMacRegionCode 40
|
|
|
|
static NSString *UTIForFlavor(uint32_t type)
|
|
{
|
|
switch (type) {
|
|
case TYPE_MOOV:
|
|
return (NSString *)kUTTypeQuickTimeMovie;
|
|
case TYPE_SND:
|
|
return (NSString *)kUTTypeAudio;
|
|
case TYPE_ICNS:
|
|
return (NSString *)kUTTypeAppleICNS;
|
|
default: {
|
|
CFStringRef typeString = UTCreateStringForOSType(type);
|
|
NSString *uti = (NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, NULL);
|
|
|
|
CFRelease(typeString);
|
|
|
|
if (uti == nil || [uti hasPrefix:@"dyn."]) {
|
|
// The docs threaten that this may stop working at some unspecified point in the future.
|
|
// However, it seems to work on Lion and Mountain Lion, and there's no other way to do this
|
|
// that I can see. Most likely, whichever release eventually breaks this will probably also
|
|
// drop support for the 32-bit applications which typically use these 32-bit scrap types anyway,
|
|
// making it irrelevant. When this happens, we should include a version check for the version of
|
|
// OS X that dropped this support, and leave uti alone in that case.
|
|
|
|
[uti release];
|
|
uti = [[NSString alloc] initWithFormat:@"CorePasteboardFlavorType 0x%08x", type];
|
|
}
|
|
|
|
return [uti autorelease];
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t FlavorForUTI(NSString *uti)
|
|
{
|
|
CFStringRef typeTag = UTTypeCopyPreferredTagWithClass((CFStringRef)uti, kUTTagClassOSType);
|
|
|
|
if (!typeTag)
|
|
return 0;
|
|
|
|
uint32_t type = UTGetOSTypeFromString(typeTag);
|
|
|
|
CFRelease(typeTag);
|
|
|
|
return type;
|
|
}
|
|
|
|
/*
|
|
* Get current system script encoding on Mac
|
|
*/
|
|
|
|
static int GetMacScriptManagerVariable(uint16_t varID)
|
|
{
|
|
int ret = -1;
|
|
M68kRegisters r;
|
|
static uint8_t proc[] = {
|
|
0x59, 0x4f, // subq.w #4,sp
|
|
0x3f, 0x3c, 0x00, 0x00, // move.w #varID,-(sp)
|
|
0x2f, 0x3c, 0x84, 0x02, 0x00, 0x08, // move.l #-2080243704,-(sp)
|
|
0xa8, 0xb5, // ScriptUtil()
|
|
0x20, 0x1f, // move.l (a7)+,d0
|
|
M68K_RTS >> 8, M68K_RTS & 0xff
|
|
};
|
|
r.d[0] = sizeof(proc);
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t proc_area = r.a[0];
|
|
if (proc_area) {
|
|
Host2Mac_memcpy(proc_area, proc, sizeof(proc));
|
|
WriteMacInt16(proc_area + 4, varID);
|
|
Execute68k(proc_area, &r);
|
|
ret = r.d[0];
|
|
r.a[0] = proc_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static ScriptCode ScriptNumberForFontID(int16_t fontID)
|
|
{
|
|
ScriptCode ret = -1;
|
|
M68kRegisters r;
|
|
static uint8_t proc[] = {
|
|
0x55, 0x4f, // subq.w #2,sp
|
|
0x3f, 0x3c, 0x00, 0x00, // move.w #fontID,-(sp)
|
|
0x2f, 0x3c, 0x82, 0x02, 0x00, 0x06, // move.l #-2113798138,-(sp)
|
|
0xa8, 0xb5, // ScriptUtil()
|
|
0x30, 0x1f, // move.w (sp)+,d0
|
|
M68K_RTS >> 8, M68K_RTS & 0xff
|
|
};
|
|
r.d[0] = sizeof(proc);
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t proc_area = r.a[0];
|
|
if (proc_area) {
|
|
Host2Mac_memcpy(proc_area, proc, sizeof(proc));
|
|
WriteMacInt16(proc_area + 4, fontID);
|
|
Execute68k(proc_area, &r);
|
|
ret = r.d[0];
|
|
r.a[0] = proc_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Get Mac's default text encoding
|
|
*/
|
|
|
|
static TextEncoding MacDefaultTextEncoding()
|
|
{
|
|
int script = GetMacScriptManagerVariable(smMacSysScript);
|
|
int region = GetMacScriptManagerVariable(smMacRegionCode);
|
|
TextEncoding encoding;
|
|
|
|
if (UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare, region, NULL, &encoding))
|
|
encoding = kTextEncodingMacRoman;
|
|
|
|
return encoding;
|
|
}
|
|
|
|
static NSData *ConvertToMacTextEncoding(NSAttributedString *aStr, NSArray **styleAndScriptRuns)
|
|
{
|
|
NSUInteger length = [aStr length];
|
|
|
|
NSMutableArray *styleRuns = [NSMutableArray array];
|
|
|
|
for (NSUInteger index = 0; index < length;) {
|
|
NSRange attrRange;
|
|
NSDictionary *attrs = [aStr attributesAtIndex:index effectiveRange:&attrRange];
|
|
|
|
[styleRuns addObject:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithUnsignedInteger:index], @"offset",
|
|
attrs, @"attributes", nil]];
|
|
|
|
index = NSMaxRange(attrRange);
|
|
}
|
|
|
|
UnicodeToTextRunInfo info;
|
|
|
|
OSStatus err = CreateUnicodeToTextRunInfoByScriptCode(0, NULL, &info);
|
|
|
|
if (err != noErr) {
|
|
if (styleAndScriptRuns)
|
|
*styleAndScriptRuns = styleRuns;
|
|
|
|
return [[aStr string] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(MacDefaultTextEncoding())];
|
|
}
|
|
|
|
unichar chars[length];
|
|
|
|
[[aStr string] getCharacters:chars range:NSMakeRange(0, length)];
|
|
|
|
ByteCount unicodeLength = length * sizeof(unichar);
|
|
ByteCount bufLen = unicodeLength * 2;
|
|
uint8_t buf[bufLen];
|
|
ByteCount bytesRead;
|
|
|
|
ItemCount scriptRunCount = 1601; // max number of allowed style changes
|
|
ScriptCodeRun scriptRuns[scriptRunCount];
|
|
|
|
ItemCount inOffsetCount = [styleRuns count];
|
|
ByteOffset inOffsets[inOffsetCount];
|
|
|
|
if (inOffsetCount) {
|
|
for (NSUInteger i = 0; i < inOffsetCount; i++) {
|
|
NSDictionary *eachRun = [styleRuns objectAtIndex:i];
|
|
|
|
inOffsets[i] = [[eachRun objectForKey:@"offset"] unsignedLongValue] * 2;
|
|
}
|
|
}
|
|
|
|
ItemCount offsetCount;
|
|
ByteOffset offsets[inOffsetCount];
|
|
|
|
err = ConvertFromUnicodeToScriptCodeRun(info, unicodeLength, chars,
|
|
kUnicodeTextRunMask | kUnicodeUseFallbacksMask | kUnicodeLooseMappingsMask,
|
|
inOffsetCount, inOffsets, &offsetCount, offsets,
|
|
bufLen, &bytesRead, &bufLen, buf,
|
|
scriptRunCount, &scriptRunCount, scriptRuns);
|
|
|
|
if (err != noErr) {
|
|
if (styleAndScriptRuns)
|
|
*styleAndScriptRuns = styleRuns;
|
|
|
|
return [[aStr string] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(MacDefaultTextEncoding())];
|
|
}
|
|
|
|
if (styleAndScriptRuns) {
|
|
NSMutableArray *runs = [NSMutableArray array];
|
|
NSUInteger currentStyleRun = 0;
|
|
NSUInteger currentScriptRun = 0;
|
|
|
|
for (NSUInteger currentOffset = 0; currentOffset < bufLen;) {
|
|
ScriptCodeRun scriptRun = scriptRuns[currentScriptRun];
|
|
NSDictionary *attrs = [[styleRuns objectAtIndex:currentStyleRun] objectForKey:@"attributes"];
|
|
|
|
NSUInteger nextStyleOffset = (currentStyleRun < offsetCount - 1) ? offsets[currentStyleRun + 1] : bufLen;
|
|
NSUInteger nextScriptOffset = (currentScriptRun < scriptRunCount - 1) ? scriptRuns[currentScriptRun + 1].offset : bufLen;
|
|
|
|
[runs addObject:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithUnsignedInteger:currentOffset], @"offset",
|
|
[NSNumber numberWithShort:scriptRun.script], @"script",
|
|
attrs, @"attributes", nil]];
|
|
|
|
if (nextStyleOffset == nextScriptOffset) {
|
|
currentStyleRun++;
|
|
currentScriptRun++;
|
|
currentOffset = nextStyleOffset;
|
|
} else if (nextStyleOffset < nextScriptOffset) {
|
|
currentStyleRun++;
|
|
currentOffset = nextStyleOffset;
|
|
} else {
|
|
currentScriptRun++;
|
|
currentOffset = nextScriptOffset;
|
|
}
|
|
}
|
|
|
|
*styleAndScriptRuns = runs;
|
|
}
|
|
|
|
return [NSData dataWithBytes:buf length:bufLen];
|
|
}
|
|
|
|
/*
|
|
* Count all Mac font IDs on the system
|
|
*/
|
|
|
|
static NSUInteger CountMacFonts()
|
|
{
|
|
M68kRegisters r;
|
|
static uint8_t proc[] = {
|
|
0x55, 0x4f, // subq.w #2,sp
|
|
0x2f, 0x3c, 'F', 'O', 'N', 'D', // move.l #'FOND',-(sp)
|
|
0xa9, 0x9c, // CountResources()
|
|
0x30, 0x1f, // move.w (sp)+,D0
|
|
M68K_RTS >> 8, M68K_RTS & 0xff
|
|
};
|
|
r.d[0] = sizeof(proc);
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t proc_area = r.a[0];
|
|
int16_t fontCount = 0;
|
|
|
|
if (proc_area) {
|
|
Host2Mac_memcpy(proc_area, proc, sizeof(proc));
|
|
Execute68k(proc_area, &r);
|
|
|
|
fontCount = r.d[0];
|
|
|
|
r.a[0] = proc_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
}
|
|
|
|
if (fontCount < 0) {
|
|
fontCount = 0;
|
|
}
|
|
|
|
return fontCount;
|
|
}
|
|
|
|
/*
|
|
* Get Mac font ID at index
|
|
*/
|
|
|
|
static int16_t MacFontIDAtIndex(NSUInteger index)
|
|
{
|
|
M68kRegisters r;
|
|
static uint8_t get_res_handle_proc[] = {
|
|
0x42, 0x27, // clr.b -(sp)
|
|
0xa9, 0x9b, // SetResLoad()
|
|
0x59, 0x4f, // subq.w #4,sp
|
|
0x2f, 0x3c, 'F', 'O', 'N', 'D', // move.l #'FOND',-(sp)
|
|
0x3f, 0x3c, 0, 0, // move.w #index,-(sp)
|
|
0xa9, 0x9d, // GetIndResource()
|
|
0x26, 0x5f, // movea.l (sp)+,A3
|
|
0x1f, 0x3c, 0x00, 0x01, // move.b #1,-(sp)
|
|
0xa9, 0x9b, // SetResLoad()
|
|
M68K_RTS >> 8, M68K_RTS & 0xff
|
|
};
|
|
r.d[0] = sizeof(get_res_handle_proc);
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t proc_area = r.a[0];
|
|
|
|
uint32_t res_handle = 0;
|
|
int16_t fontID = 0;
|
|
|
|
if (proc_area) {
|
|
Host2Mac_memcpy(proc_area, get_res_handle_proc, sizeof(get_res_handle_proc));
|
|
WriteMacInt16(proc_area + 14, (uint16_t)(index + 1));
|
|
|
|
Execute68k(proc_area, &r);
|
|
|
|
res_handle = r.a[3];
|
|
|
|
r.a[0] = proc_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr()
|
|
}
|
|
|
|
if (res_handle) {
|
|
static uint8_t get_info_proc[] = {
|
|
0x2f, 0x0a, // move.l A2,-(sp)
|
|
0x2f, 0x0b, // move.l A3,-(sp)
|
|
0x42, 0xa7, // clr.l -(sp)
|
|
0x42, 0xa7, // clr.l -(sp)
|
|
0xa9, 0xa8, // GetResInfo()
|
|
0x2f, 0x0a, // move.l A2,-(sp)
|
|
0xa9, 0xa3, // ReleaseResource()
|
|
M68K_RTS >> 8, M68K_RTS & 0xff,
|
|
0, 0
|
|
};
|
|
|
|
r.d[0] = sizeof(get_info_proc);
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
proc_area = r.a[0];
|
|
|
|
if (proc_area) {
|
|
Host2Mac_memcpy(proc_area, get_info_proc, sizeof(get_info_proc));
|
|
r.a[2] = res_handle;
|
|
r.a[3] = proc_area + 16;
|
|
|
|
Execute68k(proc_area, &r);
|
|
|
|
fontID = ReadMacInt16(proc_area + 16);
|
|
|
|
r.a[0] = proc_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr()
|
|
}
|
|
}
|
|
|
|
return fontID;
|
|
}
|
|
|
|
/*
|
|
* List all font IDs on the system
|
|
*/
|
|
|
|
static NSArray *ListMacFonts()
|
|
{
|
|
NSUInteger fontCount = CountMacFonts();
|
|
NSMutableArray *fontIDs = [NSMutableArray array];
|
|
|
|
for (NSUInteger i = 0; i < fontCount; i++) {
|
|
int16_t eachFontID = MacFontIDAtIndex(i);
|
|
|
|
[fontIDs addObject:[NSNumber numberWithShort:eachFontID]];
|
|
}
|
|
|
|
return fontIDs;
|
|
}
|
|
|
|
/*
|
|
* List all font IDs having a certain script
|
|
*/
|
|
|
|
static NSArray *ListMacFontsForScript(ScriptCode script)
|
|
{
|
|
NSMutableArray *fontIDs = [NSMutableArray array];
|
|
|
|
for (NSNumber *eachFontIDNum in ListMacFonts()) {
|
|
if (ScriptNumberForFontID([eachFontIDNum shortValue]) == script)
|
|
[fontIDs addObject:eachFontIDNum];
|
|
}
|
|
|
|
return fontIDs;
|
|
}
|
|
|
|
/*
|
|
* Convert Mac font ID to font name
|
|
*/
|
|
|
|
static NSString *FontNameFromFontID(int16_t fontID)
|
|
{
|
|
M68kRegisters r;
|
|
r.d[0] = 256; // Str255: 255 characters + length byte
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t name_area = r.a[0];
|
|
|
|
if (!name_area)
|
|
return nil;
|
|
|
|
uint8_t proc[] = {
|
|
0x3f, 0x3c, 0, 0, // move.w #fontID,-(sp)
|
|
0x2f, 0x0a, // move.l A2,-(sp)
|
|
0xa8, 0xff, // GetFontName()
|
|
M68K_RTS >> 8, M68K_RTS & 0xff
|
|
};
|
|
|
|
r.d[0] = sizeof(proc);
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t proc_area = r.a[0];
|
|
|
|
if (proc_area) {
|
|
Host2Mac_memcpy(proc_area, proc, sizeof(proc));
|
|
WriteMacInt16(proc_area + 2, fontID);
|
|
r.a[2] = name_area;
|
|
Execute68k(proc_area, &r);
|
|
|
|
r.a[0] = proc_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
}
|
|
|
|
uint8_t * const namePtr = Mac2HostAddr(name_area);
|
|
|
|
NSString *name = (NSString *)CFStringCreateWithPascalString(kCFAllocatorDefault, namePtr, kCFStringEncodingMacRoman);
|
|
|
|
r.a[0] = name_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
|
|
return [name autorelease];
|
|
}
|
|
|
|
/*
|
|
* Convert font name to Mac font ID
|
|
*/
|
|
|
|
static int16_t FontIDFromFontName(NSString *fontName)
|
|
{
|
|
M68kRegisters r;
|
|
r.d[0] = 256; // Str255: 255 characters + length byte
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t name_area = r.a[0];
|
|
|
|
if (!name_area)
|
|
return 0;
|
|
|
|
uint8_t * const namePtr = Mac2HostAddr(name_area);
|
|
|
|
CFStringGetPascalString((CFStringRef)fontName, namePtr, 256, kCFStringEncodingMacRoman);
|
|
|
|
uint8_t proc[] = {
|
|
0x2f, 0x0a, // move.l A2,-(sp)
|
|
0x2f, 0x0b, // move.l A3,-(sp)
|
|
0xa9, 0x00, // GetFNum()
|
|
M68K_RTS >> 8, M68K_RTS & 0xff,
|
|
0, 0
|
|
};
|
|
|
|
r.d[0] = sizeof(proc);
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t proc_area = r.a[0];
|
|
int16_t fontID = 0;
|
|
|
|
if (proc_area) {
|
|
Host2Mac_memcpy(proc_area, proc, sizeof(proc));
|
|
r.a[2] = name_area;
|
|
r.a[3] = proc_area + 8;
|
|
|
|
Execute68k(proc_area, &r);
|
|
|
|
fontID = ReadMacInt16(proc_area + 8);
|
|
|
|
r.a[0] = proc_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
}
|
|
|
|
r.a[0] = name_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
|
|
return fontID;
|
|
}
|
|
|
|
/*
|
|
* Get font ID in desired script if possible; otherwise, try to get some font in the desired script.
|
|
*/
|
|
|
|
static int16_t FontIDFromFontNameAndScript(NSString *fontName, ScriptCode script)
|
|
{
|
|
int16_t fontID = FontIDFromFontName(fontName);
|
|
|
|
if (ScriptNumberForFontID(fontID) == script)
|
|
return fontID;
|
|
|
|
NSArray *fontIDs = ListMacFontsForScript(script);
|
|
|
|
if ([fontIDs count] == 0)
|
|
return fontID; // no fonts are going to work; might as well return the original one
|
|
|
|
if (fontName) {
|
|
// look for a localized version of our font; e.g. "Helvetica CE" if our font is Helvetica
|
|
for (NSNumber *eachFontIDNum in fontIDs) {
|
|
int16_t eachFontID = [eachFontIDNum shortValue];
|
|
|
|
if ([FontNameFromFontID(eachFontID) hasPrefix:fontName])
|
|
return eachFontID;
|
|
}
|
|
}
|
|
|
|
// Give up and just return a font that will work
|
|
return [[fontIDs objectAtIndex:0] shortValue];
|
|
}
|
|
|
|
/*
|
|
* Convert Mac TEXT/styl to attributed string
|
|
*/
|
|
|
|
static NSAttributedString *AttributedStringFromMacTEXTAndStyl(NSData *textData, NSData *stylData)
|
|
{
|
|
NSMutableAttributedString *aStr = [[[NSMutableAttributedString alloc] init] autorelease];
|
|
|
|
if (aStr == nil)
|
|
return nil;
|
|
|
|
const uint8_t *bytes = (const uint8_t *)[stylData bytes];
|
|
NSUInteger length = [stylData length];
|
|
|
|
if (length < 2)
|
|
return nil;
|
|
|
|
uint16_t elements = CFSwapInt16BigToHost(*(uint16_t *)bytes);
|
|
const NSUInteger elementSize = 20;
|
|
|
|
if (length < elements * elementSize)
|
|
return nil;
|
|
|
|
NSUInteger cursor = 2;
|
|
|
|
for (NSUInteger i = 0; i < elements; i++) AUTORELEASE_POOL {
|
|
int32_t startChar = CFSwapInt32BigToHost(*(int32_t *)(bytes + cursor)); cursor += 4;
|
|
int16_t height __attribute__((unused)) = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
|
|
int16_t ascent __attribute__((unused)) = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
|
|
int16_t fontID = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
|
|
uint8_t face = bytes[cursor]; cursor += 2;
|
|
int16_t size = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
|
|
uint16_t red = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
|
|
uint16_t green = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
|
|
uint16_t blue = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
|
|
|
|
int32_t nextChar;
|
|
|
|
if (i + 1 == elements)
|
|
nextChar = [textData length];
|
|
else
|
|
nextChar = CFSwapInt32BigToHost(*(int32_t *)(bytes + cursor));
|
|
|
|
NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
|
|
NSColor *color = [NSColor colorWithDeviceRed:(CGFloat)red / 65535.0 green:(CGFloat)green / 65535.0 blue:(CGFloat)blue / 65535.0 alpha:1.0];
|
|
NSFont *font;
|
|
TextEncoding encoding;
|
|
|
|
if (fontID == 0) { // System font
|
|
CGFloat fontSize = (size == 0) ? [NSFont systemFontSize] : (CGFloat)size;
|
|
font = [NSFont systemFontOfSize:fontSize];
|
|
} else if (fontID == 1) { // Application font
|
|
font = [NSFont userFontOfSize:(CGFloat)size];
|
|
} else {
|
|
NSString *fontName = FontNameFromFontID(fontID);
|
|
font = [NSFont fontWithName:fontName size:(CGFloat)size];
|
|
|
|
if (font == nil) {
|
|
// Convert localized variants of fonts; e.g. "Helvetica CE" to "Helvetica"
|
|
|
|
NSRange wsRange = [fontName rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet] options:NSBackwardsSearch];
|
|
|
|
if (wsRange.length) {
|
|
fontName = [fontName substringToIndex:wsRange.location];
|
|
font = [NSFont fontWithName:fontName size:(CGFloat)size];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (font == nil)
|
|
font = [NSFont userFontOfSize:(CGFloat)size];
|
|
|
|
if (UpgradeScriptInfoToTextEncoding(ScriptNumberForFontID(fontID), kTextLanguageDontCare, kTextRegionDontCare, NULL, &encoding))
|
|
encoding = MacDefaultTextEncoding();
|
|
|
|
NSFontManager *fm = [NSFontManager sharedFontManager];
|
|
|
|
if (face & FONT_FACE_BOLD)
|
|
font = [fm convertFont:font toHaveTrait:NSBoldFontMask];
|
|
|
|
if (face & FONT_FACE_ITALIC)
|
|
font = [fm convertFont:font toHaveTrait:NSItalicFontMask];
|
|
|
|
if (face & FONT_FACE_CONDENSED)
|
|
font = [fm convertFont:font toHaveTrait:NSCondensedFontMask];
|
|
|
|
if (face & FONT_FACE_EXTENDED)
|
|
font = [fm convertFont:font toHaveTrait:NSExpandedFontMask];
|
|
|
|
[attrs setObject:font forKey:NSFontAttributeName];
|
|
|
|
if (face & FONT_FACE_UNDERLINE)
|
|
[attrs setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
|
|
|
|
if (face & FONT_FACE_OUTLINE) {
|
|
[attrs setObject:color forKey:NSStrokeColorAttributeName];
|
|
[attrs setObject:[NSNumber numberWithInteger:3] forKey:NSStrokeWidthAttributeName];
|
|
}
|
|
|
|
if (face & FONT_FACE_SHADOW) {
|
|
NSShadow *shadow = [[NSShadow alloc] init];
|
|
NSColor *shadowColor = [NSColor colorWithDeviceRed:(CGFloat)red / 65535.0 green:(CGFloat)green / 65535.0 blue:(CGFloat)blue / 65535.0 alpha:0.5];
|
|
|
|
[shadow setShadowColor:shadowColor];
|
|
[shadow setShadowOffset:NSMakeSize(2, -2.0)];
|
|
|
|
[attrs setObject:shadow forKey:NSShadowAttributeName];
|
|
|
|
[shadow release];
|
|
}
|
|
|
|
[attrs setObject:color forKey:NSForegroundColorAttributeName];
|
|
|
|
NSData *partialData = [textData subdataWithRange:NSMakeRange(startChar, nextChar - startChar)];
|
|
NSString *partialString = [[NSString alloc] initWithData:partialData encoding:CFStringConvertEncodingToNSStringEncoding(encoding)];
|
|
|
|
if (partialString) {
|
|
NSAttributedString *partialAttribString = [[NSAttributedString alloc] initWithString:partialString attributes:attrs];
|
|
|
|
[aStr appendAttributedString:partialAttribString];
|
|
|
|
[partialAttribString release];
|
|
}
|
|
|
|
[partialString release];
|
|
[attrs release];
|
|
}
|
|
|
|
return aStr;
|
|
}
|
|
|
|
/*
|
|
* Append styl data for one text run
|
|
*/
|
|
|
|
static void AppendStylRunData(NSMutableData *stylData, NSDictionary *attrs, ScriptCode script)
|
|
{
|
|
NSFontManager *fontManager = [NSFontManager sharedFontManager];
|
|
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
|
|
|
|
NSFont *font = [attrs objectForKey:NSFontAttributeName];
|
|
NSColor *color = [[attrs objectForKey:NSForegroundColorAttributeName] colorUsingColorSpaceName:NSDeviceRGBColorSpace device:nil];
|
|
NSFontTraitMask traits = [fontManager traitsOfFont:font];
|
|
NSNumber *underlineStyle = [attrs objectForKey:NSUnderlineStyleAttributeName];
|
|
NSNumber *strokeWidth = [attrs objectForKey:NSStrokeWidthAttributeName];
|
|
NSShadow *shadow = [attrs objectForKey:NSShadowAttributeName];
|
|
|
|
int16_t hostFontID = FontIDFromFontNameAndScript([font familyName], script);
|
|
|
|
if (hostFontID == 0) {
|
|
hostFontID = [font isFixedPitch] ? 4 /* Monaco */ : 1 /* Application font */;
|
|
}
|
|
|
|
int16_t height = CFSwapInt16HostToBig((int16_t)rint([layoutManager defaultLineHeightForFont:font]));
|
|
int16_t ascent = CFSwapInt16HostToBig((int16_t)rint([font ascender]));
|
|
int16_t fontID = CFSwapInt16HostToBig(hostFontID);
|
|
uint8_t face = 0;
|
|
int16_t size = CFSwapInt16HostToBig((int16_t)rint([font pointSize]));
|
|
uint16_t red = CFSwapInt16HostToBig((int16_t)rint([color redComponent] * 65535.0));
|
|
uint16_t green = CFSwapInt16HostToBig((int16_t)rint([color greenComponent] * 65535.0));
|
|
uint16_t blue = CFSwapInt16HostToBig((int16_t)rint([color blueComponent] * 65535.0));
|
|
|
|
if (traits & NSBoldFontMask) {
|
|
face |= FONT_FACE_BOLD;
|
|
}
|
|
|
|
if (traits & NSItalicFontMask) {
|
|
face |= FONT_FACE_ITALIC;
|
|
}
|
|
|
|
if (traits & NSCondensedFontMask) {
|
|
face |= FONT_FACE_CONDENSED;
|
|
}
|
|
|
|
if (traits & NSExpandedFontMask) {
|
|
face |= FONT_FACE_EXTENDED;
|
|
}
|
|
|
|
if (underlineStyle && [underlineStyle integerValue] != NSUnderlineStyleNone) {
|
|
face |= FONT_FACE_UNDERLINE;
|
|
}
|
|
|
|
if (strokeWidth && [strokeWidth doubleValue] > 0.0) {
|
|
face |= FONT_FACE_OUTLINE;
|
|
}
|
|
|
|
if (shadow) {
|
|
face |= FONT_FACE_SHADOW;
|
|
}
|
|
|
|
[stylData appendBytes:&height length:2];
|
|
[stylData appendBytes:&ascent length:2];
|
|
[stylData appendBytes:&fontID length:2];
|
|
[stylData appendBytes:&face length:1];
|
|
[stylData increaseLengthBy:1];
|
|
[stylData appendBytes:&size length:2];
|
|
[stylData appendBytes:&red length:2];
|
|
[stylData appendBytes:&green length:2];
|
|
[stylData appendBytes:&blue length:2];
|
|
|
|
[layoutManager release];
|
|
}
|
|
|
|
/*
|
|
* Convert attributed string to TEXT/styl
|
|
*/
|
|
|
|
static NSData *ConvertToMacTEXTAndStyl(NSAttributedString *aStr, NSData **outStylData)
|
|
{
|
|
// Limitations imposed by the Mac TextEdit system.
|
|
const NSUInteger charLimit = 32 * 1024;
|
|
const NSUInteger elementLimit = 1601;
|
|
|
|
NSUInteger length = [aStr length];
|
|
|
|
if (length > charLimit) {
|
|
aStr = [aStr attributedSubstringFromRange:NSMakeRange(0, charLimit)];
|
|
}
|
|
|
|
NSArray *runs = nil;
|
|
|
|
NSData *textData = ConvertToMacTextEncoding(aStr, &runs);
|
|
|
|
NSMutableData *stylData = [NSMutableData dataWithLength:2]; // number of styles to be filled in at the end
|
|
|
|
NSUInteger elements = 0;
|
|
|
|
for (NSDictionary *eachRun in runs) {
|
|
if (elements >= elementLimit)
|
|
break;
|
|
|
|
NSUInteger offset = [[eachRun objectForKey:@"offset"] unsignedIntegerValue];
|
|
ScriptCode script = [[eachRun objectForKey:@"script"] shortValue];
|
|
NSDictionary *attrs = [eachRun objectForKey:@"attributes"];
|
|
|
|
if (![attrs count]) continue;
|
|
|
|
int32_t startChar = CFSwapInt32HostToBig((int32_t)offset);
|
|
[stylData appendBytes:&startChar length:4];
|
|
|
|
AppendStylRunData(stylData, attrs, script);
|
|
|
|
elements++;
|
|
}
|
|
|
|
uint16_t bigEndianElements = CFSwapInt16HostToBig((uint16_t)elements);
|
|
|
|
[stylData replaceBytesInRange:NSMakeRange(0, 2) withBytes:&bigEndianElements length:2];
|
|
|
|
if (outStylData)
|
|
*outStylData = stylData;
|
|
|
|
return textData;
|
|
}
|
|
|
|
/*
|
|
* Get data of a particular flavor from the pasteboard
|
|
*/
|
|
|
|
static NSData *DataFromPasteboard(NSPasteboard *pboard, NSString *flavor)
|
|
{
|
|
return [pboard dataForType:flavor];
|
|
}
|
|
|
|
/*
|
|
* Convert Mac TEXT/styl to RTF
|
|
*/
|
|
|
|
static void WriteMacTEXTAndStylToPasteboard(NSPasteboard *pboard, NSData *textData, NSData *stylData)
|
|
{
|
|
NSMutableAttributedString *aStr = [AttributedStringFromMacTEXTAndStyl(textData, stylData) mutableCopy];
|
|
|
|
if (!aStr) {
|
|
NSString *string = [[NSString alloc] initWithData:textData encoding:CFStringConvertEncodingToNSStringEncoding(MacDefaultTextEncoding())];
|
|
|
|
if (!string)
|
|
return;
|
|
|
|
aStr = [[NSMutableAttributedString alloc] initWithString:string attributes:nil];
|
|
|
|
[string release];
|
|
}
|
|
|
|
// fix line endings
|
|
[[aStr mutableString] replaceOccurrencesOfString:@"\r" withString:@"\n" options:NSLiteralSearch range:NSMakeRange(0, [aStr length])];
|
|
|
|
[pboard writeObjects:[NSArray arrayWithObject:aStr]];
|
|
|
|
[aStr release];
|
|
}
|
|
|
|
/*
|
|
* Convert RTF to Mac TEXT/styl
|
|
*/
|
|
|
|
static NSData *MacTEXTAndStylDataFromPasteboard(NSPasteboard *pboard, NSData **outStylData)
|
|
{
|
|
NSMutableAttributedString *aStr;
|
|
|
|
NSArray *objs = [pboard readObjectsForClasses:[NSArray arrayWithObject:[NSAttributedString class]] options:nil];
|
|
|
|
if ([objs count]) {
|
|
aStr = [[objs objectAtIndex:0] mutableCopy];
|
|
} else {
|
|
objs = [pboard readObjectsForClasses:[NSArray arrayWithObject:[NSString class]] options:nil];
|
|
|
|
if (![objs count])
|
|
return nil;
|
|
|
|
aStr = [[NSMutableAttributedString alloc] initWithString:[objs objectAtIndex:0]];
|
|
}
|
|
|
|
// fix line endings
|
|
[[aStr mutableString] replaceOccurrencesOfString:@"\n" withString:@"\r" options:NSLiteralSearch range:NSMakeRange(0, [[aStr mutableString] length])];
|
|
|
|
NSData *stylData = nil;
|
|
NSData *textData = ConvertToMacTEXTAndStyl(aStr, &stylData);
|
|
|
|
[aStr release];
|
|
|
|
if (outStylData)
|
|
*outStylData = stylData;
|
|
|
|
return textData;
|
|
}
|
|
|
|
/*
|
|
* Initialization
|
|
*/
|
|
|
|
void ClipInit(void)
|
|
{
|
|
g_pboard = [[NSPasteboard generalPasteboard] retain];
|
|
if (!g_pboard) {
|
|
D(bug("could not create Pasteboard\n"));
|
|
}
|
|
|
|
g_macScrap = [[NSMutableDictionary alloc] init];
|
|
}
|
|
|
|
|
|
/*
|
|
* Deinitialization
|
|
*/
|
|
|
|
void ClipExit(void)
|
|
{
|
|
[g_pboard release];
|
|
g_pboard = nil;
|
|
|
|
[g_macScrap release];
|
|
g_macScrap = nil;
|
|
}
|
|
|
|
/*
|
|
* Convert an NSImage to PICT format.
|
|
*/
|
|
|
|
static NSData *ConvertImageToPICT(NSImage *image) {
|
|
if ([[image representations] count] == 0) {
|
|
return nil;
|
|
}
|
|
|
|
NSImageRep *rep = [[image representations] objectAtIndex:0];
|
|
NSUInteger width;
|
|
NSUInteger height;
|
|
|
|
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
|
|
width = [rep pixelsWide];
|
|
height = [rep pixelsHigh];
|
|
} else {
|
|
width = lrint([image size].width);
|
|
height = lrint([image size].height);
|
|
}
|
|
|
|
// create a new bitmap image rep in our desired format, following the advice here:
|
|
// https://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKitOlderNotes.html#X10_6Notes
|
|
|
|
NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
|
pixelsWide:width
|
|
pixelsHigh:height
|
|
bitsPerSample:8
|
|
samplesPerPixel:4
|
|
hasAlpha:YES
|
|
isPlanar:NO
|
|
colorSpaceName:NSCalibratedRGBColorSpace
|
|
bytesPerRow:width * 4
|
|
bitsPerPixel:32];
|
|
|
|
[NSGraphicsContext saveGraphicsState];
|
|
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
|
|
[rep draw];
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
|
|
unsigned char *rgba = [bitmap bitmapData];
|
|
|
|
long bufSize = ConvertRGBAToPICT(NULL, 0, rgba, width, height);
|
|
|
|
NSData *pictData = nil;
|
|
|
|
if (bufSize > 0) {
|
|
uint8_t *buf = (uint8_t *)malloc(bufSize);
|
|
|
|
long pictSize = ConvertRGBAToPICT(buf, bufSize, rgba, width, height);
|
|
|
|
if (pictSize > 0)
|
|
pictData = [NSData dataWithBytes:buf length:pictSize];
|
|
|
|
free(buf);
|
|
}
|
|
|
|
[bitmap release];
|
|
|
|
return pictData;
|
|
}
|
|
|
|
/*
|
|
* Convert any images that may be on the clipboard to PICT format if possible.
|
|
*/
|
|
|
|
static NSData *MacPICTDataFromPasteboard(NSPasteboard *pboard)
|
|
{
|
|
// check if there's any PICT data on the pasteboard
|
|
NSData *pictData = DataFromPasteboard(pboard, (NSString *)kUTTypePICT);
|
|
|
|
if (pictData)
|
|
return pictData;
|
|
|
|
// now check to see if any images on the pasteboard have PICT representations
|
|
NSArray *objs = [pboard readObjectsForClasses:[NSArray arrayWithObject:[NSImage class]] options:nil];
|
|
|
|
for (NSImage *eachImage in objs) {
|
|
for (NSImageRep *eachRep in [eachImage representations]) {
|
|
|
|
if ([eachRep isKindOfClass:[NSPICTImageRep class]])
|
|
return [(NSPICTImageRep *)eachRep PICTRepresentation];
|
|
}
|
|
}
|
|
|
|
// Give up and perform the conversion ourselves
|
|
if ([objs count])
|
|
return ConvertImageToPICT([objs objectAtIndex:0]);
|
|
|
|
// If none of that worked, sorry, we're out of options
|
|
return nil;
|
|
}
|
|
|
|
/*
|
|
* Zero Mac clipboard
|
|
*/
|
|
|
|
static void ZeroMacClipboard()
|
|
{
|
|
D(bug("Zeroing Mac clipboard\n"));
|
|
M68kRegisters r;
|
|
static uint8_t proc[] = {
|
|
0x59, 0x8f, // subq.l #4,sp
|
|
0xa9, 0xfc, // ZeroScrap()
|
|
0x58, 0x8f, // addq.l #4,sp
|
|
M68K_RTS >> 8, M68K_RTS & 0xff
|
|
};
|
|
r.d[0] = sizeof(proc);
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t proc_area = r.a[0];
|
|
|
|
if (proc_area) {
|
|
Host2Mac_memcpy(proc_area, proc, sizeof(proc));
|
|
Execute68k(proc_area, &r);
|
|
|
|
[g_macScrap removeAllObjects];
|
|
|
|
r.a[0] = proc_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write data to Mac clipboard
|
|
*/
|
|
|
|
static void WriteDataToMacClipboard(NSData *pbData, uint32_t type)
|
|
{
|
|
D(bug("Writing data %s to Mac clipboard with type '%c%c%c%c'\n", [[pbData description] UTF8String],
|
|
(type >> 24) & 0xff, (type >> 16) & 0xff, (type >> 8) & 0xff, type & 0xff));
|
|
|
|
if ([pbData length] == 0)
|
|
return;
|
|
|
|
NSNumber *typeNum = [NSNumber numberWithInteger:type];
|
|
|
|
if ([g_macScrap objectForKey:typeNum]) {
|
|
// the classic Mac OS can't have more than one object of the same type on the clipboard
|
|
return;
|
|
}
|
|
|
|
// Allocate space for new scrap in MacOS side
|
|
M68kRegisters r;
|
|
r.d[0] = [pbData length];
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t scrap_area = r.a[0];
|
|
|
|
// Get the native clipboard data
|
|
if (scrap_area) {
|
|
uint8_t * const data = Mac2HostAddr(scrap_area);
|
|
|
|
memcpy(data, [pbData bytes], [pbData length]);
|
|
|
|
// Add new data to clipboard
|
|
static uint8_t proc[] = {
|
|
0x59, 0x8f, // subq.l #4,sp
|
|
0x2f, 0x3c, 0, 0, 0, 0, // move.l #length,-(sp)
|
|
0x2f, 0x3c, 0, 0, 0, 0, // move.l #type,-(sp)
|
|
0x2f, 0x3c, 0, 0, 0, 0, // move.l #outbuf,-(sp)
|
|
0xa9, 0xfe, // PutScrap()
|
|
0x58, 0x8f, // addq.l #4,sp
|
|
M68K_RTS >> 8, M68K_RTS & 0xff
|
|
};
|
|
r.d[0] = sizeof(proc);
|
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
|
|
uint32_t proc_area = r.a[0];
|
|
|
|
if (proc_area) {
|
|
Host2Mac_memcpy(proc_area, proc, sizeof(proc));
|
|
WriteMacInt32(proc_area + 4, [pbData length]);
|
|
WriteMacInt32(proc_area + 10, type);
|
|
WriteMacInt32(proc_area + 16, scrap_area);
|
|
we_put_this_data = true;
|
|
Execute68k(proc_area, &r);
|
|
|
|
r.a[0] = proc_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
|
|
[g_macScrap setObject:pbData forKey:typeNum];
|
|
}
|
|
|
|
r.a[0] = scrap_area;
|
|
Execute68kTrap(0xa01f, &r); // DisposePtr
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Take all the data on host pasteboard and convert it to something the Mac understands if possible
|
|
*/
|
|
|
|
static void ConvertHostPasteboardToMacScrap()
|
|
{
|
|
D(bug("ConvertHostPasteboardToMacScrap\n"));
|
|
|
|
ZeroMacClipboard();
|
|
|
|
NSData *stylData = nil;
|
|
NSData *textData = MacTEXTAndStylDataFromPasteboard(g_pboard, &stylData);
|
|
|
|
if (textData) {
|
|
if (stylData && [stylData length] > 2)
|
|
WriteDataToMacClipboard(stylData, TYPE_STYL);
|
|
|
|
WriteDataToMacClipboard(textData, TYPE_TEXT);
|
|
}
|
|
|
|
NSData *pictData = MacPICTDataFromPasteboard(g_pboard);
|
|
|
|
if (pictData)
|
|
WriteDataToMacClipboard(pictData, TYPE_PICT);
|
|
|
|
for (NSString *eachType in [g_pboard types]) {
|
|
if (UTTypeConformsTo((CFStringRef)eachType, kUTTypeText)) {
|
|
// text types are already handled
|
|
continue;
|
|
}
|
|
|
|
if (UTTypeConformsTo((CFStringRef)eachType, kUTTypeImage)) {
|
|
// image types are already handled
|
|
continue;
|
|
}
|
|
|
|
uint32_t type = FlavorForUTI(eachType);
|
|
|
|
// skip styl and ustl as well; those fall under text, which is handled already
|
|
if (!type || type == TYPE_STYL || type == TYPE_USTL)
|
|
continue;
|
|
|
|
WriteDataToMacClipboard(DataFromPasteboard(g_pboard, eachType), type);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Take all the data on the Mac clipbord and convert it to something the host pasteboard understands if possible
|
|
*/
|
|
|
|
static void ConvertMacScrapToHostPasteboard()
|
|
{
|
|
D(bug("ConvertMacScrapToHostPasteboard\n"));
|
|
|
|
BOOL wroteText = NO;
|
|
|
|
[g_pboard clearContents];
|
|
|
|
for (NSNumber *eachTypeNum in g_macScrap) AUTORELEASE_POOL {
|
|
uint32_t eachType = [eachTypeNum integerValue];
|
|
|
|
if (eachType == TYPE_TEXT || eachType == TYPE_STYL || eachType == TYPE_UTXT || eachType == TYPE_UT16 || eachType == TYPE_USTL) {
|
|
if (wroteText)
|
|
continue;
|
|
|
|
NSData *textData;
|
|
NSData *stylData;
|
|
|
|
textData = [g_macScrap objectForKey:[NSNumber numberWithInteger:TYPE_TEXT]];
|
|
stylData = [g_macScrap objectForKey:[NSNumber numberWithInteger:TYPE_STYL]];
|
|
|
|
if (textData) {
|
|
WriteMacTEXTAndStylToPasteboard(g_pboard, textData, stylData);
|
|
wroteText = YES;
|
|
}
|
|
|
|
// sometime, it might be interesting to write a converter for utxt/ustl if possible
|
|
|
|
continue;
|
|
}
|
|
|
|
NSData *pbData = [g_macScrap objectForKey:eachTypeNum];
|
|
|
|
if (pbData) {
|
|
NSString *typeStr = UTIForFlavor(eachType);
|
|
|
|
if (!typeStr)
|
|
continue;
|
|
|
|
[g_pboard setData:pbData forType:typeStr];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check whether the pasteboard has changed since our last check; if it has, write it to the emulated pasteboard
|
|
*/
|
|
|
|
static void ConvertHostPasteboardToMacScrapIfChanged()
|
|
{
|
|
if (!g_pboard)
|
|
return;
|
|
|
|
if ([g_pboard changeCount] > g_pb_change_count) {
|
|
ConvertHostPasteboardToMacScrap();
|
|
g_pb_change_count = [g_pboard changeCount];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mac application reads clipboard
|
|
*/
|
|
|
|
void GetScrap(void **handle, uint32_t type, int32_t offset)
|
|
{
|
|
D(bug("GetScrap handle %p, type %4.4s, offset %d\n", handle, (char *)&type, offset));
|
|
|
|
AUTORELEASE_POOL {
|
|
ConvertHostPasteboardToMacScrapIfChanged();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ZeroScrap() is called before a Mac application writes to the clipboard; clears out the previous contents
|
|
*/
|
|
|
|
void ZeroScrap()
|
|
{
|
|
D(bug("ZeroScrap\n"));
|
|
|
|
we_put_this_data = false;
|
|
|
|
// Defer clearing the host pasteboard until the Mac tries to put something on it.
|
|
// This prevents us from clearing the pasteboard when ZeroScrap() is called during startup.
|
|
should_clear = true;
|
|
}
|
|
|
|
/*
|
|
* Mac application wrote to clipboard
|
|
*/
|
|
|
|
void PutScrap(uint32_t type, void *scrap, int32_t length)
|
|
{
|
|
D(bug("PutScrap type %4.4s, data %p, length %ld\n", (char *)&type, scrap, (long)length));
|
|
|
|
AUTORELEASE_POOL {
|
|
if (!g_pboard)
|
|
return;
|
|
|
|
if (we_put_this_data) {
|
|
we_put_this_data = false;
|
|
return;
|
|
}
|
|
|
|
if (length <= 0)
|
|
return;
|
|
|
|
if (should_clear) {
|
|
[g_macScrap removeAllObjects];
|
|
should_clear = false;
|
|
}
|
|
|
|
NSData *pbData = [NSData dataWithBytes:scrap length:length];
|
|
if (!pbData)
|
|
return;
|
|
|
|
[g_macScrap setObject:pbData forKey:[NSNumber numberWithInteger:type]];
|
|
|
|
ConvertMacScrapToHostPasteboard();
|
|
|
|
// So that our PutScrap() patch won't bounce the data we just wrote back to the Mac clipboard
|
|
g_pb_change_count = [g_pboard changeCount];
|
|
}
|
|
}
|