diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index 734f126a1b..9a67ee0161 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -34,7 +34,7 @@ 1AFD334123E03C4F0042899B /* controlhost.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AFD334023E03C4F0042899B /* controlhost.mm */; }; 37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; }; 37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; }; - 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; }; + 37C09D8821580FE4006A6758 /* StorageProvider.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* StorageProvider.mm */; }; 37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; }; 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; }; 520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; }; @@ -95,7 +95,7 @@ 379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = ""; }; 37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = ""; }; 37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = ""; }; - 37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = ""; }; + 37C09D8721580FE4006A6758 /* StorageProvider.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = StorageProvider.mm; sourceTree = ""; }; 37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = ""; }; 37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = ""; }; 37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = ""; }; @@ -202,7 +202,7 @@ 523484CB26EA68AA00EA0C2C /* trayicon.h */, 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */, 37A517B22159597E00FBA241 /* Screens.mm */, - 37C09D8721580FE4006A6758 /* SystemDialogs.mm */, + 37C09D8721580FE4006A6758 /* StorageProvider.mm */, EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */, AB7A61F02147C815003C5833 /* Products */, AB661C1C2148230E00291242 /* Frameworks */, @@ -339,7 +339,7 @@ 1AFD334123E03C4F0042899B /* controlhost.mm in Sources */, 1A465D10246AB61600C5858B /* dnd.mm in Sources */, AB00E4F72147CA920032A60A /* main.mm in Sources */, - 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */, + 37C09D8821580FE4006A6758 /* StorageProvider.mm in Sources */, 1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */, F10084862BFF1FB40024303E /* TopLevelImpl.mm in Sources */, 1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */, diff --git a/native/Avalonia.Native/src/OSX/SystemDialogs.mm b/native/Avalonia.Native/src/OSX/StorageProvider.mm similarity index 85% rename from native/Avalonia.Native/src/OSX/SystemDialogs.mm rename to native/Avalonia.Native/src/OSX/StorageProvider.mm index 97c8108edc..0fd77c6789 100644 --- a/native/Avalonia.Native/src/OSX/SystemDialogs.mm +++ b/native/Avalonia.Native/src/OSX/StorageProvider.mm @@ -64,12 +64,91 @@ const int kFileTypePopupTag = 10975; @end -class SystemDialogs : public ComSingleObject +class StorageProvider : public ComSingleObject { ExtensionDropdownHandler* __strong _extension_dropdown_handler; public: FORWARD_IUNKNOWN() + + virtual HRESULT SaveBookmarkToBytes ( + IAvnString* fileUriStr, + void** err, + IAvnString** ppv + ) override + { + @autoreleasepool + { + if(ppv == nullptr) + return E_POINTER; + + NSError* error; + auto fileUri = [NSURL URLWithString: GetNSStringAndRelease(fileUriStr)]; + auto bookmarkData = [fileUri bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; + if (bookmarkData) + { + *ppv = CreateByteArray((void*)bookmarkData.bytes, (int)bookmarkData.length); + } + if (error != nil) + { + *err = CreateAvnString([error localizedDescription]); + } + return S_OK; + } + } + + virtual HRESULT ReadBookmarkFromBytes ( + void* ptr, + int len, + IAvnString** ppv + ) override { + @autoreleasepool + { + if(ppv == nullptr) + return E_POINTER; + + auto bookmarkData = [[NSData alloc] initWithBytes:ptr length:len]; + auto fileUri = [NSURL URLByResolvingBookmarkData: bookmarkData + options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI + relativeToURL:nil + bookmarkDataIsStale:nil + error:nil]; + + if (fileUri) + { + *ppv = CreateAvnString([fileUri absoluteString]); + } + return S_OK; + } + } + + virtual void ReleaseBookmark ( + IAvnString* fileUriStr + ) override { + // no-op + } + + virtual bool OpenSecurityScope ( + IAvnString* fileUriStr + ) override { + @autoreleasepool + { + auto fileUri = [NSURL URLWithString: GetNSStringAndRelease(fileUriStr)]; + auto success = [fileUri startAccessingSecurityScopedResource]; + return success; + } + } + + virtual void CloseSecurityScope ( + IAvnString* fileUriStr + ) override { + @autoreleasepool + { + auto fileUri = [NSURL URLWithString: GetNSStringAndRelease(fileUriStr)]; + [fileUri stopAccessingSecurityScopedResource]; + } + } + virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle, IAvnSystemDialogEvents* events, bool allowMultiple, @@ -105,19 +184,9 @@ public: if(urls.count > 0) { - void* strings[urls.count]; - - for(int i = 0; i < urls.count; i++) - { - auto url = [urls objectAtIndex:i]; - - auto string = [url path]; - - strings[i] = (void*)[string UTF8String]; - } - - events->OnCompleted((int)urls.count, &strings[0]); - + auto uriStrings = CreateAvnStringArray(urls); + events->OnCompleted(uriStrings); + [panel orderOut:panel]; if(parentWindowHandle != nullptr) @@ -130,7 +199,7 @@ public: } } - events->OnCompleted(0, nullptr); + events->OnCompleted(nullptr); }; @@ -188,19 +257,9 @@ public: if(urls.count > 0) { - void* strings[urls.count]; - - for(int i = 0; i < urls.count; i++) - { - auto url = [urls objectAtIndex:i]; - - auto string = [url path]; - - strings[i] = (void*)[string UTF8String]; - } - - events->OnCompleted((int)urls.count, &strings[0]); - + auto uriStrings = CreateAvnStringArray(urls); + events->OnCompleted(uriStrings); + [panel orderOut:panel]; if(parentWindowHandle != nullptr) @@ -213,7 +272,7 @@ public: } } - events->OnCompleted(0, nullptr); + events->OnCompleted(nullptr); }; @@ -264,15 +323,11 @@ public: auto handler = ^(NSModalResponse result) { if(result == NSFileHandlingPanelOKButton) { - void* strings[1]; - auto url = [panel URL]; - - auto string = [url path]; - strings[0] = (void*)[string UTF8String]; - - events->OnCompleted(1, &strings[0]); - + auto urls = [NSArray arrayWithObject:url]; + auto uriStrings = CreateAvnStringArray(urls); + events->OnCompleted(uriStrings); + [panel orderOut:panel]; if(parentWindowHandle != nullptr) @@ -284,7 +339,7 @@ public: return; } - events->OnCompleted(0, nullptr); + events->OnCompleted(nullptr); }; @@ -519,7 +574,7 @@ private: }; }; -extern IAvnSystemDialogs* CreateSystemDialogs() +extern IAvnStorageProvider* CreateStorageProvider() { - return new SystemDialogs(); + return new StorageProvider(); } diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index fab11d6e4f..36c157704d 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -14,7 +14,7 @@ extern void PostDispatcherCallback(IAvnActionCallback* cb); extern IAvnTopLevel* CreateAvnTopLevel(IAvnTopLevelEvents* events); extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events); extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events); -extern IAvnSystemDialogs* CreateSystemDialogs(); +extern IAvnStorageProvider* CreateStorageProvider(); extern IAvnScreens* CreateScreens(IAvnScreenEvents* cb); extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*); extern NSPasteboardItem* TryGetPasteboardItem(IAvnClipboard*); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 47a5ba73c5..d1dbe9d186 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -276,13 +276,13 @@ public: } } - virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) override + virtual HRESULT CreateStorageProvider(IAvnStorageProvider** ppv) override { START_COM_CALL; @autoreleasepool { - *ppv = ::CreateSystemDialogs(); + *ppv = ::CreateStorageProvider(); return S_OK; } } diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index fc66f533c6..5b7814fb5d 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -254,17 +254,24 @@ namespace ControlCatalog.Pages if (file is not null) { - // Sync disposal of StreamWriter is not supported on WASM + try + { + // Sync disposal of StreamWriter is not supported on WASM #if NET6_0_OR_GREATER - await using var stream = await file.OpenWriteAsync(); - await using var writer = new System.IO.StreamWriter(stream); + await using var stream = await file.OpenWriteAsync(); + await using var writer = new System.IO.StreamWriter(stream); #else - using var stream = await file.OpenWriteAsync(); - using var writer = new System.IO.StreamWriter(stream); + using var stream = await file.OpenWriteAsync(); + using var writer = new System.IO.StreamWriter(stream); #endif - await writer.WriteLineAsync(openedFileContent.Text); + await writer.WriteLineAsync(openedFileContent.Text); - SetFolder(await file.GetParentAsync()); + SetFolder(await file.GetParentAsync()); + } + catch (Exception ex) + { + openedFileContent.Text = ex.ToString(); + } } await SetPickerResult(file is null ? null : new[] { file }); @@ -280,8 +287,6 @@ namespace ControlCatalog.Pages }); await SetPickerResult(folders); - - SetFolder(folders.FirstOrDefault()); }; this.Get