Repository: unoplatform/uno (2026-04-07T03:08:00Z)

Issue:
Linux file open picker does not work with paths containing spaces
### Current behavior 🐛

If a file with a path containing a space is selected, the returned StorageFile objected will contain "%20" in the path instead of a space. Opening the StorageFile object with OpenAsync() returns a stream with zero size.

### Expected behavior 🎯

_No response_

### How to reproduce it (as minimally and precisely as possible) 🔬

_No response_

### Workaround 🛠️

_No response_

### Renderer 🎨

- [x] Skia
- [ ] Native

### Affected platforms 📱💻🖥️

Desktop (X11)

### Uno.Sdk version (and other relevant versions) 📦

6.5.31

### IDE version 🧑‍💻

_No response_

### Anything else we need to know? 💬

_No response_


Discussion:

Patch:
diff --git a/src/Uno.UI.Runtime.Skia.X11/Storage/Pickers/LinuxFilePickerExtension.cs b/src/Uno.UI.Runtime.Skia.X11/Storage/Pickers/LinuxFilePickerExtension.cs
index 44ebca96ec55..5dafac7fec41 100644
--- a/src/Uno.UI.Runtime.Skia.X11/Storage/Pickers/LinuxFilePickerExtension.cs
+++ b/src/Uno.UI.Runtime.Skia.X11/Storage/Pickers/LinuxFilePickerExtension.cs
@@ -185,7 +185,7 @@ public async Task<IReadOnlyList<string>> PickFilesAsync(CancellationToken token,
 				return ImmutableList<string>.Empty;
 			}
 
-			return results["uris"].GetArray<string>().Select(s => new Uri(s).AbsolutePath).ToImmutableList();
+			return results["uris"].GetArray<string>().Select(s => new Uri(s).LocalPath).ToImmutableList();
 		}
 		catch (Exception e)
 		{
diff --git a/src/Uno.UI.Runtime.Skia.X11/Storage/Pickers/LinuxFileSaverExtension.cs b/src/Uno.UI.Runtime.Skia.X11/Storage/Pickers/LinuxFileSaverExtension.cs
index bd60c96d84d2..04fd8c489e3b 100644
--- a/src/Uno.UI.Runtime.Skia.X11/Storage/Pickers/LinuxFileSaverExtension.cs
+++ b/src/Uno.UI.Runtime.Skia.X11/Storage/Pickers/LinuxFileSaverExtension.cs
@@ -164,7 +164,7 @@ internal class LinuxFileSaverExtension(FileSavePicker picker) : IFileSavePickerE
 				return null;
 			}
 
-			return StorageFile.GetFileFromPath(new Uri(results["uris"].GetArray<string>()[0]).AbsolutePath);
+			return StorageFile.GetFileFromPath(new Uri(results["uris"].GetArray<string>()[0]).LocalPath);
 		}
 		catch (Exception e)
 		{


Test patch:
diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_Storage/Given_StorageFile.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_Storage/Given_StorageFile.cs
index 58dcbf8643b7..c752e7574988 100644
--- a/src/Uno.UI.RuntimeTests/Tests/Windows_Storage/Given_StorageFile.cs
+++ b/src/Uno.UI.RuntimeTests/Tests/Windows_Storage/Given_StorageFile.cs
@@ -406,6 +406,42 @@ public async Task When_Copy_Saf()
 		}
 #endif
 
+		[TestMethod]
+		[PlatformCondition(ConditionMode.Exclude, RuntimeTestPlatforms.NativeWinUI)]
+		public async Task When_GetFileFromPath_With_Spaces_OpenAsync_Returns_Content()
+		{
+			var folder = ApplicationData.Current.LocalFolder;
+			var fileName = "test file with spaces.txt";
+			var expectedContent = "hello world";
+
+			var file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
+			await FileIO.WriteTextAsync(file, expectedContent);
+
+			try
+			{
+				// Simulate the portal URI → path conversion that X11 pickers perform.
+				// The xdg-desktop-portal returns file:// URIs where spaces are %20-encoded.
+				var fileUri = new Uri(file.Path);
+				Assert.IsTrue(fileUri.AbsoluteUri.Contains("%20"), "URI should contain percent-encoded spaces");
+
+				// Uri.LocalPath decodes %20 → space; Uri.AbsolutePath does not.
+				var decodedPath = fileUri.LocalPath;
+				Assert.IsFalse(decodedPath.Contains("%20"), "LocalPath should decode percent-encoded characters");
+
+				var storageFile = await StorageFile.GetFileFromPathAsync(decodedPath);
+				using var stream = await storageFile.OpenStreamForReadAsync();
+				Assert.IsTrue(stream.Length > 0, "Stream from file with spaces in path should not be empty");
+
+				using var reader = new StreamReader(stream);
+				var content = await reader.ReadToEndAsync();
+				Assert.AreEqual(expectedContent, content);
+			}
+			finally
+			{
+				await file.DeleteAsync();
+			}
+		}
+
 		private string GetRandomFilePath()
 			=> Path.Combine(ApplicationData.Current.LocalFolder.Path, $"{Guid.NewGuid()}.txt");