Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Files.App/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,17 @@ static UserEnvironmentPaths()
{ NetworkFolderPath.ToUpperInvariant(), NetworkFolderPath },
};
}

public static class Distributions
{
public static readonly string[] KnownAppNames =
{
"49306atecsolution.FilesUWP", // store stable
"FilesStable", // sideload stable
"FilesPreview", // sideload preview
"49306atecsolution.FilesPreview", // store preview
"FilesDev", // dev
};
}
}
}
5 changes: 0 additions & 5 deletions src/Files.App/Helpers/Win32/Win32Helper.Process.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,5 @@ public static List<Process> WhoIsLocking(string[] resources)

return processes;
}

public static Task<string> GetFileAssociationAsync(string filePath)
{
return GetFileAssociationAsync(filePath, true);
}
}
}
119 changes: 100 additions & 19 deletions src/Files.App/Helpers/Win32/Win32Helper.Storage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License. See the LICENSE.

using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using System.Collections.Concurrent;
using System.Drawing;
Expand Down Expand Up @@ -163,28 +164,15 @@ public static Task StartSTATask(Action action)
return taskCompletionSource.Task;
}

public static async Task<string?> GetFileAssociationAsync(string filename, bool checkDesktopFirst = false)
public static async Task<string?> GetDefaultFileAssociationAsync(string filename, bool checkDesktopFirst = true)
{
// Find UWP apps
async Task<string?> GetUwpAssoc()
{
var uwpApps = await Launcher.FindFileHandlersAsync(Path.GetExtension(filename));
return uwpApps.Any() ? uwpApps[0].PackageFamilyName : null;
}
// check if there exists an user choice first
var userChoice = GetUserChoiceFileAssociation(filename);
if (!string.IsNullOrEmpty(userChoice))
return userChoice;

// Find desktop apps
string? GetDesktopAssoc()
{
var lpResult = new StringBuilder(2048);
var hResult = Shell32.FindExecutable(filename, null, lpResult);
return await GetFileAssociationAsync(filename, checkDesktopFirst);

return hResult.ToInt64() > 32 ? lpResult.ToString() : null;
}

if (checkDesktopFirst)
return GetDesktopAssoc() ?? await GetUwpAssoc();

return await GetUwpAssoc() ?? GetDesktopAssoc();
}

public static string ExtractStringFromDLL(string file, int number)
Expand Down Expand Up @@ -1210,5 +1198,98 @@ public static bool GetWin32FindDataForPath(string targetPath, out Win32PInvoke.W

return false;
}

private static string? GetPackageFamilyNameFromAppRegistryName(string appRegistryName)
{
using var appXKey = Registry.ClassesRoot.OpenSubKey(appRegistryName + @"\Application");
var appUserModelIdObj = appXKey?.GetValue("AppUserModelId");
string? appUserModelId = appUserModelIdObj?.ToString();
string? packageFamilyName = null;
if (!string.IsNullOrEmpty(appUserModelId))
{
int bangIndex = appUserModelId.IndexOf('!');
packageFamilyName = bangIndex > 0 ? appUserModelId[..bangIndex] : appUserModelId;
}

return packageFamilyName;
}

private static string? GetUserChoiceFileAssociation(string filename)
{
var fileExtension = Path.GetExtension(filename);
if (string.IsNullOrEmpty(filename))
return null;

try
{
// Get ProgId from UserChoice
using var userChoiceKey = Registry.CurrentUser.OpenSubKey($@"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{fileExtension}\UserChoice");
var progIdObj = userChoiceKey?.GetValue("ProgId");
string? progId = progIdObj?.ToString();

if (string.IsNullOrEmpty(progId))
return null;

// Get the package family name if it's an AppX app
if (progId.StartsWith("AppX", StringComparison.OrdinalIgnoreCase))
{
string? packageFamilyName = GetPackageFamilyNameFromAppRegistryName(progId);
if (!string.IsNullOrEmpty(packageFamilyName))
return packageFamilyName;
}

// Find the open command for the ProgId
using var commandKey = Registry.ClassesRoot.OpenSubKey($@"{progId}\shell\open\command");
var command = commandKey?.GetValue(null)?.ToString();

if (string.IsNullOrEmpty(command))
return null;

// Extract executable path from command string (e.g. "\"C:\\Program Files\\App\\app.exe\" \"%1\"")
var exePath = command.Trim();
if (exePath.StartsWith("\""))
{
int endQuote = exePath.IndexOf('\"', 1);
if (endQuote > 1)
exePath = exePath.Substring(1, endQuote - 1);
}
else
{
int firstSpace = exePath.IndexOf(' ');
if (firstSpace > 0)
exePath = exePath.Substring(0, firstSpace);
}

return File.Exists(exePath) ? exePath : null;
}
catch
{
return null;
}
}

private static async Task<string?> GetFileAssociationAsync(string filename, bool checkDesktopFirst = true)
{
// Find UWP apps
async Task<string?> GetUwpAssoc()
{
var uwpApps = await Launcher.FindFileHandlersAsync(Path.GetExtension(filename));
return uwpApps.Any() ? uwpApps[0].PackageFamilyName : null;
}

// Find desktop apps
string? GetDesktopAssoc()
{
var lpResult = new StringBuilder(2048);
var hResult = Shell32.FindExecutable(filename, null, lpResult);

return hResult.ToInt64() > 32 ? lpResult.ToString() : null;
}

if (checkDesktopFirst)
return GetDesktopAssoc() ?? await GetUwpAssoc();

return await GetUwpAssoc() ?? GetDesktopAssoc();
}
}
}
2 changes: 1 addition & 1 deletion src/Files.App/Utils/Shell/LaunchHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ private static async Task<bool> HandleApplicationLaunch(string application, stri
var groups = split.GroupBy(x => new
{
Dir = Path.GetDirectoryName(x),
Prog = Win32Helper.GetFileAssociationAsync(x).Result ?? Path.GetExtension(x)
Prog = Win32Helper.GetDefaultFileAssociationAsync(x).Result ?? Path.GetExtension(x)
});

foreach (var group in groups)
Expand Down
5 changes: 3 additions & 2 deletions src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,11 @@ public static async Task<bool> CheckDefaultZipApp(string filePath)
{
Func<Task<bool>> queryFileAssoc = async () =>
{
var assoc = await Win32Helper.GetFileAssociationAsync(filePath);
var assoc = await Win32Helper.GetDefaultFileAssociationAsync(filePath);
if (assoc is not null)
{
return assoc == Package.Current.Id.FamilyName
return Constants.Distributions.KnownAppNames.Any(x => assoc.StartsWith(x, StringComparison.OrdinalIgnoreCase))
|| assoc == Package.Current.Id.FamilyName
|| assoc.EndsWith("Files.App\\Files.exe", StringComparison.OrdinalIgnoreCase)
|| assoc.Equals(IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"), StringComparison.OrdinalIgnoreCase);
}
Expand Down
Loading