#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include "common.h"
#include "clipboard.h"
#include "FrnString.h"
class Clipboard : public ComSingleObject<IFrnClipboard, &IID_IFrnClipboard>
{
private:
NSPasteboard* _pasteboard;
public:
FORWARD_IUNKNOWN()
Clipboard(NSPasteboard* pasteboard)
{
if (pasteboard == nil)
pasteboard = [NSPasteboard generalPasteboard];
_pasteboard = pasteboard;
}
virtual HRESULT GetFormats(int64_t changeCount, IFrnStringArray** ret) override
{
START_COM_ARP_CALL;
if (ret == nullptr)
return E_POINTER;
if (changeCount != [_pasteboard changeCount])
return COR_E_OBJECTDISPOSED;
*ret = ConvertPasteboardTypes([_pasteboard types]);
return S_OK;
}
virtual HRESULT GetItemCount(int64_t changeCount, int* ret) override
{
START_COM_ARP_CALL;
if (ret == nullptr)
return E_POINTER;
if (changeCount != [_pasteboard changeCount])
return COR_E_OBJECTDISPOSED;
auto items = [_pasteboard pasteboardItems];
*ret = items == nil ? 0 : (int)[items count];
return S_OK;
}
virtual HRESULT GetItemFormats(int index, int64_t changeCount, IFrnStringArray** ret) override
{
START_COM_ARP_CALL;
if (ret == nullptr)
return E_POINTER;
if (changeCount != [_pasteboard changeCount])
return COR_E_OBJECTDISPOSED;
auto item = [[_pasteboard pasteboardItems] objectAtIndex:index];
*ret = ConvertPasteboardTypes([item types]);
return S_OK;
}
static IFrnStringArray* ConvertPasteboardTypes(NSArray<NSPasteboardType> *types)
{
if (types != nil)
{
NSMutableArray<NSString *> *mutableTypes = [types mutableCopy];
if (![mutableTypes containsObject:NSPasteboardTypePNG])
{
if ([mutableTypes containsObject:NSPasteboardTypeTIFF]
|| [mutableTypes containsObject:@"public.jpeg"])
{
[mutableTypes addObject: NSPasteboardTypePNG];
}
}
return CreateFrnStringArray(mutableTypes);
}
return nil;
}
virtual HRESULT GetItemValueAsString(int index, int64_t changeCount, const char* format, IFrnString** ret) override
{
START_COM_ARP_CALL;
if (ret == nullptr)
return E_POINTER;
if (changeCount != [_pasteboard changeCount])
return COR_E_OBJECTDISPOSED;
auto item = [[_pasteboard pasteboardItems] objectAtIndex:index];
auto value = [item stringForType:[NSString stringWithUTF8String:format]];
*ret = value == nil ? nullptr : CreateFrnString(value);
return S_OK;
}
virtual HRESULT GetItemValueAsBytes(int index, int64_t changeCount, const char* format, IFrnString** ret) override
{
START_COM_ARP_CALL;
if (ret == nullptr)
return E_POINTER;
if (changeCount != [_pasteboard changeCount])
return COR_E_OBJECTDISPOSED;
auto item = [[_pasteboard pasteboardItems] objectAtIndex:index];
auto formatStr = [NSString stringWithUTF8String:format];
auto value = [item dataForType: formatStr];
if (value == nil && [formatStr isEqualToString: NSPasteboardTypePNG])
{
NSData *imageData = nil;
imageData = [item dataForType:NSPasteboardTypeTIFF];
if (imageData == nil) {
imageData = [item dataForType:@"public.jpeg"];
}
if (imageData != nil)
{
auto image = [[NSImage alloc] initWithData:imageData];
NSBitmapImageRep *bitmapRep = nil;
for (NSImageRep *rep in image.representations) {
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
bitmapRep = (NSBitmapImageRep *)rep;
break;
}
}
if (!bitmapRep) {
[image lockFocus];
bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, image.size.width, image.size.height)];
[image unlockFocus];
}
value = [bitmapRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
}
}
*ret = value == nil || [value length] == 0
? nullptr
: CreateByteArray((void*)[value bytes], (int)[value length]);
return S_OK;
}
virtual HRESULT Clear(int64_t* ret) override
{
START_COM_ARP_CALL;
*ret = [_pasteboard clearContents];
return S_OK;
}
virtual HRESULT GetChangeCount(int64_t* ret) override
{
START_COM_ARP_CALL;
*ret = [_pasteboard changeCount];
return S_OK;
}
virtual HRESULT SetData(IFrnClipboardDataSource* source) override
{
START_COM_ARP_CALL;
auto count = source->GetItemCount();
auto writeableItems = [NSMutableArray<WriteableClipboardItem*> arrayWithCapacity:count];
for (auto i = 0; i < count; ++i)
{
ComPtr<IFrnClipboardDataItem> item;
if(S_OK != source->GetItem(i, item.getPPV()))
continue;
auto writeableItem = [[WriteableClipboardItem alloc] initWithItem:item source:source];
[writeableItems addObject:writeableItem];
}
[_pasteboard writeObjects:writeableItems];
return S_OK;
}
virtual bool IsTextFormat(const char *format) override
{
START_COM_ARP_CALL;
auto formatString = [NSString stringWithUTF8String:format];
if (@available(macOS 11.0, *))
{
auto type = [UTType typeWithIdentifier:formatString];
return type != nil && [type conformsToType:UTTypeText];
}
else
{
return UTTypeConformsTo((__bridge CFStringRef)formatString, kUTTypeText);
}
}
};
extern IFrnClipboard* CreateClipboard(NSPasteboard* pb)
{
return new Clipboard(pb);
}
@implementation WriteableClipboardItem
{
ComPtr<IFrnClipboardDataItem> _item;
ComPtr<IFrnClipboardDataSource> _source;
}
- (nonnull WriteableClipboardItem*) initWithItem:(nonnull IFrnClipboardDataItem*)item source:(nonnull IFrnClipboardDataSource*)source
{
self = [super init];
_item = item;
_source = source;
return self;
}
NSString* TryConvertFormatToUti(NSString* format)
{
if (@available(macOS 11.0, *))
{
auto type = [UTType typeWithIdentifier:format];
if (type == nil)
{
if ([format containsString:@"/"])
type = [UTType typeWithMIMEType:format];
else
type = [UTType exportedTypeWithIdentifier:format];
if (type == nil)
{
auto fromPasteboardType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassNSPboardType, (__bridge CFStringRef)format, nil);
if (fromPasteboardType != nil)
return (__bridge_transfer NSString*)fromPasteboardType;
}
}
return type == nil ? nil : [type identifier];
}
else
{
auto bridgedFormat = (__bridge CFStringRef)format;
if (UTTypeIsDeclared(bridgedFormat))
return format;
auto fromMimeType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, bridgedFormat, nil);
if (fromMimeType != nil)
return (__bridge_transfer NSString*)fromMimeType;
auto fromPasteboardType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassNSPboardType, bridgedFormat, nil);
if (fromPasteboardType != nil)
return (__bridge_transfer NSString*)fromPasteboardType;
return nil;
}
}
- (nonnull NSArray<NSPasteboardType>*) writableTypesForPasteboard:(nonnull NSPasteboard*)pasteboard
{
ComPtr<IFrnStringArray> formats;
auto hr = _item->ProvideFormats(formats.getPPV());
if (hr != S_OK || formats == nullptr)
return [NSArray array];
auto count = formats->GetCount();
if (count == 0)
return [NSArray array];
auto utis = [NSMutableArray arrayWithCapacity:count];
IFrnString* format;
for (auto i = 0; i < count; ++i)
{
if (formats->Get(i, &format) != S_OK)
continue;
auto formatString = GetNSStringAndRelease(format);
auto uti = TryConvertFormatToUti(formatString);
if (uti != nil)
[utis addObject:uti];
}
[utis addObject:GetFrnCustomDataType()];
return utis;
}
- (NSPasteboardWritingOptions) writingOptionsForType:(NSPasteboardType)type pasteboard:(NSPasteboard*)pasteboard
{
return [type isEqualToString:NSPasteboardTypeString] || [type isEqualToString:GetFrnCustomDataType()]
? 0
: NSPasteboardWritingPromised;
}
- (nullable id) pasteboardPropertyListForType:(nonnull NSPasteboardType)type
{
if ([type isEqualToString:GetFrnCustomDataType()])
return @"";
ComPtr<IFrnClipboardDataValue> value;
HRESULT hr = _item->GetValue([type UTF8String], value.getPPV());
if (hr != S_OK || value.getRaw() == nullptr)
return nil;
if (value->IsString())
return GetNSStringAndRelease(value->AsString());
auto length = value->GetByteLength();
auto buffer = malloc(length);
value->CopyBytesTo(buffer);
return [NSData dataWithBytesNoCopy:buffer length:length];
}
@end