macemu/BasiliskII/src/MacOSX/clip_macosx64.mm

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];
}
}