A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

354 lines
11 KiB

#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include "common.h"
#include "clipboard.h"
#include "AvnString.h"
class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard>
{
private:
NSPasteboard* _pasteboard;
public:
FORWARD_IUNKNOWN()
Clipboard(NSPasteboard* pasteboard)
{
if (pasteboard == nil)
pasteboard = [NSPasteboard generalPasteboard];
_pasteboard = pasteboard;
}
virtual HRESULT GetFormats(int64_t changeCount, IAvnStringArray** 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, IAvnStringArray** 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 IAvnStringArray* ConvertPasteboardTypes(NSArray<NSPasteboardType> *types)
{
if (types != nil)
{
NSMutableArray<NSString *> *mutableTypes = [types mutableCopy];
// Add png if format list doesn't have PNG,
// but has any other image type that can be converter into PNG
if (![mutableTypes containsObject:NSPasteboardTypePNG])
{
if ([mutableTypes containsObject:NSPasteboardTypeTIFF]
|| [mutableTypes containsObject:@"public.jpeg"])
{
[mutableTypes addObject: NSPasteboardTypePNG];
}
}
return CreateAvnStringArray(mutableTypes);
}
return nil;
}
virtual HRESULT GetItemValueAsString(int index, int64_t changeCount, const char* format, IAvnString** 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 : CreateAvnString(value);
return S_OK;
}
virtual HRESULT GetItemValueAsBytes(int index, int64_t changeCount, const char* format, IAvnString** 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 PNG wasn't found, try to convert TIFF or JPEG to PNG
if (value == nil && [formatStr isEqualToString: NSPasteboardTypePNG])
{
NSData *imageData = nil;
// Try TIFF first
imageData = [item dataForType:NSPasteboardTypeTIFF];
// If no TIFF, try JPEG
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(IAvnClipboardDataSource* source) override
{
START_COM_ARP_CALL;
auto count = source->GetItemCount();
auto writeableItems = [NSMutableArray<WriteableClipboardItem*> arrayWithCapacity:count];
for (auto i = 0; i < count; ++i)
{
auto item = source->GetItem(i);
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 IAvnClipboard* CreateClipboard(NSPasteboard* pb)
{
return new Clipboard(pb);
}
@implementation WriteableClipboardItem
{
IAvnClipboardDataItem* _item;
IAvnClipboardDataSource* _source;
}
- (nonnull WriteableClipboardItem*) initWithItem:(nonnull IAvnClipboardDataItem*)item source:(nonnull IAvnClipboardDataSource*)source
{
self = [super init];
_item = item;
_source = source;
// Each item references its source so it doesn't get disposed too early.
source->AddRef();
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)
{
// For now, we need to use the deprecated UTTypeCreatePreferredIdentifierForTag to create a dynamic UTI for arbitrary strings.
// This is only necessary because the old IDataObject can provide arbitrary types that aren't UTIs nor mime types.
// With the new DataFormat:
// - If the format is an application format, the managed side provides a UTI like net.avaloniaui.app.uti.xxx.
// - If the format is an OS format, the user has been warned that they MUST provide a name which is valid for the OS.
// TODO12: remove!
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
{
auto formats = _item->ProvideFormats();
if (formats == nullptr)
return [NSArray array];
auto count = formats->GetCount();
if (count == 0)
return [NSArray array];
auto utis = [NSMutableArray arrayWithCapacity:count];
IAvnString* format;
for (auto i = 0; i < count; ++i)
{
if (formats->Get(i, &format) != S_OK)
continue;
// Only UTIs must be returned from writableTypesForPasteboard or an exception will be thrown
auto formatString = GetNSStringAndRelease(format);
auto uti = TryConvertFormatToUti(formatString);
if (uti != nil)
[utis addObject:uti];
}
formats->Release();
[utis addObject:GetAvnCustomDataType()];
return utis;
}
- (NSPasteboardWritingOptions) writingOptionsForType:(NSPasteboardType)type pasteboard:(NSPasteboard*)pasteboard
{
return [type isEqualToString:NSPasteboardTypeString] || [type isEqualToString:GetAvnCustomDataType()]
? 0
: NSPasteboardWritingPromised;
}
- (nullable id) pasteboardPropertyListForType:(nonnull NSPasteboardType)type
{
if ([type isEqualToString:GetAvnCustomDataType()])
return @"";
ComPtr<IAvnClipboardDataValue> value(_item->GetValue([type UTF8String]), true);
if (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];
}
- (void) dealloc
{
if (_item != nullptr)
{
_item->Release();
_item = nullptr;
}
if (_source != nullptr)
{
_source->Release();
_source = nullptr;
}
}
@end