FreeRDP
Loading...
Searching...
No Matches
client_cliprdr_file_apple.m
1
20#include <stdlib.h>
21#include <string.h>
22#include <unistd.h>
23
24#import <AppKit/AppKit.h>
25#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
26
27#include "client_cliprdr_file_apple.h"
28
29static const char s_mime_uri_list[] = "text/uri-list";
30static const char s_mime_text_utf8[] = "text/plain;charset=utf-8";
31
32static NSArray<NSURL *> *get_file_urls(void)
33{
34 NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
35 NSDictionary *options = @{NSPasteboardURLReadingFileURLsOnlyKey: @YES};
36 return [pasteboard readObjectsForClasses:@[[NSURL class]] options:options];
37}
38
39char *cliprdr_file_apple_get_uri_list(size_t *size)
40{
41 @autoreleasepool
42 {
43 NSMutableString *list = [NSMutableString string];
44
45 for (NSURL *url in get_file_urls())
46 {
47 /* Finder puts file reference URLs (file:///.file/id=...) on the pasteboard */
48 NSURL *path_url = [url filePathURL];
49 if (!path_url)
50 continue;
51 [list appendFormat:@"%@\r\n", [path_url absoluteString]];
52 }
53
54 if ([list length] == 0)
55 return NULL;
56
57 char *data = strdup([list UTF8String]);
58 if (data && size)
59 *size = strlen(data);
60 return data;
61 }
62}
63
64static cliprdr_file_apple_provide_file_fkt s_file_fkt = NULL;
65static void *s_file_userdata = NULL;
66static cliprdr_file_context_data_fkt s_data_fkt = NULL;
67static void *s_data_userdata = NULL;
68static NSUInteger s_generation = 0;
69static NSMutableArray *s_providers = nil;
70static unsigned s_download_serial = 0;
71
72@interface CliprdrFileProvider : NSObject <NSPasteboardItemDataProvider>
73@property(nonatomic) size_t index;
74@property(nonatomic) NSUInteger generation;
75/* pasteboard type -> MIME type the data is requested with */
76@property(nonatomic, strong) NSDictionary<NSPasteboardType, NSString *> *mimeTypes;
77@end
78
79@implementation CliprdrFileProvider
80
81- (void)pasteboard:(NSPasteboard *)pasteboard
82 item:(NSPasteboardItem *)item
83 provideDataForType:(NSPasteboardType)type
84{
85 if (self.generation != s_generation)
86 return;
87
88 if ([type isEqualToString:NSPasteboardTypeFileURL])
89 {
90 if (!s_file_fkt)
91 return;
92
93 char *path = s_file_fkt(s_file_userdata, self.index);
94 if (!path)
95 return;
96
97 NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path]];
98 free(path);
99 [item setString:[url absoluteString] forType:NSPasteboardTypeFileURL];
100 return;
101 }
102
103 NSString *mime = self.mimeTypes[type];
104 if (!mime || !s_data_fkt)
105 return;
106
107 size_t size = 0;
108 const void *data = s_data_fkt(s_data_userdata, [mime UTF8String], &size);
109 if (!data)
110 return;
111
112 if ([type isEqualToString:NSPasteboardTypeString])
113 {
114 /* Applications that paste text get the text of the copy, e.g. the paths */
115 NSString *text = [[NSString alloc] initWithBytes:data
116 length:size
117 encoding:NSUTF8StringEncoding];
118 if (text)
119 [item setString:text forType:type];
120 }
121 else
122 [item setData:[NSData dataWithBytes:data length:size] forType:type];
123}
124
125@end
126
127/* Text becomes the native string, text/uri-list the file items. Everything else keeps its
128 * own type, so the caller still recognises the copy as its own (e.g. by a private type)
129 * when the pasteboard change comes back to it. */
130API_AVAILABLE(macos(11.0))
131static NSDictionary<NSPasteboardType, NSString *> *get_item_types(const char **mime_types,
132 size_t mime_count)
133{
134 NSMutableDictionary<NSPasteboardType, NSString *> *types = [NSMutableDictionary dictionary];
135
136 for (size_t i = 0; i < mime_count; i++)
137 {
138 const char *mime = mime_types[i];
139 if (!mime || (strcmp(mime, s_mime_uri_list) == 0))
140 continue;
141
142 if (strncmp(mime, "text/", 5) == 0)
143 {
144 types[NSPasteboardTypeString] = @(s_mime_text_utf8);
145 continue;
146 }
147
148 NSString *mime_type = [NSString stringWithUTF8String:mime];
149 UTType *uttype = mime_type ? [UTType typeWithMIMEType:mime_type] : nil;
150 if (uttype && !types[uttype.identifier])
151 types[uttype.identifier] = mime_type;
152 }
153 return types;
154}
155
156BOOL cliprdr_file_apple_offer(size_t count, cliprdr_file_apple_provide_file_fkt file_fkt,
157 void *file_userdata, const char **mime_types, size_t mime_count,
158 cliprdr_file_context_data_fkt data_fkt, void *data_userdata)
159{
160 if (@available(macOS 11.0, *))
161 {
162 @autoreleasepool
163 {
164 s_file_fkt = file_fkt;
165 s_file_userdata = file_userdata;
166 s_data_fkt = data_fkt;
167 s_data_userdata = data_userdata;
168 s_generation++;
169
170 NSDictionary<NSPasteboardType, NSString *> *mimeTypes =
171 get_item_types(mime_types, mime_count);
172
173 /* NSPasteboardItem does not keep its data provider alive */
174 s_providers = [NSMutableArray array];
175 NSMutableArray *items = [NSMutableArray array];
176 for (size_t i = 0; i < count; i++)
177 {
178 CliprdrFileProvider *provider = [CliprdrFileProvider new];
179 provider.index = i;
180 provider.generation = s_generation;
181 [s_providers addObject:provider];
182
183 NSMutableArray<NSPasteboardType> *types =
184 [NSMutableArray arrayWithObject:NSPasteboardTypeFileURL];
185 if (i == 0)
186 {
187 provider.mimeTypes = mimeTypes;
188 [types addObjectsFromArray:[mimeTypes allKeys]];
189 }
190
191 NSPasteboardItem *item = [NSPasteboardItem new];
192 if (![item setDataProvider:provider forTypes:types])
193 return FALSE;
194 [items addObject:item];
195 }
196
197 NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
198 /* Universal Clipboard reads new contents right away, which would download
199 * every copied file. Keep the files on this Mac until they are pasted. */
200 [pasteboard prepareForNewContentsWithOptions:NSPasteboardContentsCurrentHostOnly];
201 return [pasteboard writeObjects:items];
202 }
203 }
204 return FALSE;
205}
206
207static NSString *download_root(void)
208{
209 NSString *name = [NSString stringWithFormat:@"freerdp-clipboard-%d", getpid()];
210 return [NSTemporaryDirectory() stringByAppendingPathComponent:name];
211}
212
213char *cliprdr_file_apple_new_download_dir(void)
214{
215 @autoreleasepool
216 {
217 NSFileManager *fm = [NSFileManager defaultManager];
218 NSString *root = download_root();
219
220 /* Keep the previous offer, Finder might still be copying from it */
221 if (s_download_serial >= 2)
222 {
223 NSString *old = [NSString stringWithFormat:@"%u", s_download_serial - 2];
224 [fm removeItemAtPath:[root stringByAppendingPathComponent:old] error:nil];
225 }
226
227 NSString *name = [NSString stringWithFormat:@"%u", s_download_serial++];
228 NSString *dir = [root stringByAppendingPathComponent:name];
229 NSDictionary *attributes = @{NSFilePosixPermissions: @0700};
230 if (![fm createDirectoryAtPath:dir
231 withIntermediateDirectories:YES
232 attributes:attributes
233 error:nil])
234 return NULL;
235 return strdup([dir fileSystemRepresentation]);
236 }
237}
238
239void cliprdr_file_apple_detach(void)
240{
241 @autoreleasepool
242 {
243 s_file_fkt = NULL;
244 s_file_userdata = NULL;
245 s_data_fkt = NULL;
246 s_data_userdata = NULL;
247 s_generation++;
248 s_providers = nil;
249 [[NSFileManager defaultManager] removeItemAtPath:download_root() error:nil];
250 }
251}