2022-01-25 20:31:57 +02:00
|
|
|
|
// Copyright (c) Microsoft Corporation
|
|
|
|
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
|
|
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Text;
|
2024-09-16 16:09:43 -04:00
|
|
|
|
|
2022-01-25 20:31:57 +02:00
|
|
|
|
using Wox.Plugin.Common.Win32;
|
|
|
|
|
|
using Wox.Plugin.Logger;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Wox.Plugin.Common
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Contains information (e.g. path to executable, name...) about the default browser.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static class DefaultBrowserInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
private static readonly object _updateLock = new object();
|
|
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
/// <summary>Gets the path to the MS Edge browser executable.</summary>
|
2024-08-07 09:38:40 +02:00
|
|
|
|
public static string MSEdgePath => System.IO.Path.Combine(
|
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
|
|
|
|
|
|
@"Microsoft\Edge\Application\msedge.exe");
|
2022-04-12 17:10:05 +02:00
|
|
|
|
|
|
|
|
|
|
/// <summary>Gets the command line pattern of the MS Edge.</summary>
|
2024-08-07 09:38:40 +02:00
|
|
|
|
public const string MSEdgeArgumentsPattern = "--single-argument %1";
|
2022-01-25 20:31:57 +02:00
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
public const string MSEdgeName = "Microsoft Edge";
|
2022-01-25 20:31:57 +02:00
|
|
|
|
|
|
|
|
|
|
/// <summary>Gets the path to default browser's executable.</summary>
|
|
|
|
|
|
public static string Path { get; private set; }
|
|
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
/// <summary>Gets <see cref="Path"/> since the icon is embedded in the executable.</summary>
|
|
|
|
|
|
public static string IconPath => Path;
|
2022-01-25 20:31:57 +02:00
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
/// <summary>Gets the user-friendly name of the default browser.</summary>
|
2022-01-25 20:31:57 +02:00
|
|
|
|
public static string Name { get; private set; }
|
|
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
/// <summary>Gets the command line pattern of the default browser.</summary>
|
|
|
|
|
|
public static string ArgumentsPattern { get; private set; }
|
2022-01-25 20:31:57 +02:00
|
|
|
|
|
|
|
|
|
|
public static bool IsDefaultBrowserSet { get => !string.IsNullOrEmpty(Path); }
|
|
|
|
|
|
|
2022-06-14 21:04:30 +01:00
|
|
|
|
public const long UpdateTimeout = 300;
|
|
|
|
|
|
|
|
|
|
|
|
private static long _lastUpdateTickCount = -UpdateTimeout;
|
|
|
|
|
|
|
2024-08-07 09:38:40 +02:00
|
|
|
|
private static bool _updatedOnce;
|
|
|
|
|
|
private static bool _errorLogged;
|
2022-01-25 20:31:57 +02:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Updates only if at least more than 300ms has passed since the last update, to avoid multiple calls to <see cref="Update"/>.
|
|
|
|
|
|
/// (because of multiple plugins calling update at the same time.)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static void UpdateIfTimePassed()
|
|
|
|
|
|
{
|
2022-06-14 21:04:30 +01:00
|
|
|
|
long curTickCount = Environment.TickCount64;
|
|
|
|
|
|
if (curTickCount - _lastUpdateTickCount >= UpdateTimeout)
|
2022-01-25 20:31:57 +02:00
|
|
|
|
{
|
|
|
|
|
|
_lastUpdateTickCount = curTickCount;
|
|
|
|
|
|
Update();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Consider using <see cref="UpdateIfTimePassed"/> to avoid updating multiple times.
|
|
|
|
|
|
/// (because of multiple plugins calling update at the same time.)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static void Update()
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_updateLock)
|
|
|
|
|
|
{
|
2024-08-07 09:38:40 +02:00
|
|
|
|
if (!_updatedOnce)
|
2022-06-14 21:04:30 +01:00
|
|
|
|
{
|
2024-08-07 09:38:40 +02:00
|
|
|
|
Log.Info("I've tried updating the chosen Web Browser info at least once.", typeof(DefaultBrowserInfo));
|
|
|
|
|
|
_updatedOnce = true;
|
2022-06-14 21:04:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-25 20:31:57 +02:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
string progId = GetRegistryValue(
|
|
|
|
|
|
@"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice",
|
|
|
|
|
|
"ProgId");
|
2022-04-12 17:10:05 +02:00
|
|
|
|
string appName = GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}\Application", "ApplicationName")
|
|
|
|
|
|
?? GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}", "FriendlyTypeName");
|
2022-01-25 20:31:57 +02:00
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
if (appName != null)
|
2022-01-25 20:31:57 +02:00
|
|
|
|
{
|
2022-04-12 17:10:05 +02:00
|
|
|
|
// Handle indirect strings:
|
2023-12-28 13:37:13 +03:00
|
|
|
|
if (appName.StartsWith('@'))
|
2022-01-25 20:31:57 +02:00
|
|
|
|
{
|
2022-04-12 17:10:05 +02:00
|
|
|
|
appName = GetIndirectString(appName);
|
2022-01-25 20:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
appName = appName
|
|
|
|
|
|
.Replace("URL", null, StringComparison.OrdinalIgnoreCase)
|
|
|
|
|
|
.Replace("HTML", null, StringComparison.OrdinalIgnoreCase)
|
|
|
|
|
|
.Replace("Document", null, StringComparison.OrdinalIgnoreCase)
|
|
|
|
|
|
.Replace("Web", null, StringComparison.OrdinalIgnoreCase)
|
|
|
|
|
|
.TrimEnd();
|
2022-01-25 20:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
Name = appName;
|
2022-01-25 20:31:57 +02:00
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
string commandPattern = GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}\shell\open\command", null);
|
2022-01-25 20:31:57 +02:00
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
if (string.IsNullOrEmpty(commandPattern))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentOutOfRangeException(
|
|
|
|
|
|
nameof(commandPattern),
|
|
|
|
|
|
"Default browser program command is not specified.");
|
2022-01-25 20:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
if (commandPattern.StartsWith('@'))
|
|
|
|
|
|
{
|
|
|
|
|
|
commandPattern = GetIndirectString(commandPattern);
|
|
|
|
|
|
}
|
2022-01-25 20:31:57 +02:00
|
|
|
|
|
2022-09-21 14:31:45 +01:00
|
|
|
|
// HACK: for firefox installed through Microsoft store
|
|
|
|
|
|
// When installed through Microsoft Firefox the commandPattern does not have
|
|
|
|
|
|
// quotes for the path. As the Program Files does have a space
|
|
|
|
|
|
// the extracted path would be invalid, here we add the quotes to fix it
|
|
|
|
|
|
const string FirefoxExecutableName = "firefox.exe";
|
|
|
|
|
|
if (commandPattern.Contains(FirefoxExecutableName) && commandPattern.Contains(@"\WindowsApps\") && (!commandPattern.StartsWith('\"')))
|
|
|
|
|
|
{
|
|
|
|
|
|
var pathEndIndex = commandPattern.IndexOf(FirefoxExecutableName, StringComparison.Ordinal) + FirefoxExecutableName.Length;
|
|
|
|
|
|
commandPattern = commandPattern.Insert(pathEndIndex, "\"");
|
|
|
|
|
|
commandPattern = commandPattern.Insert(0, "\"");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
if (commandPattern.StartsWith('\"'))
|
2022-01-25 20:31:57 +02:00
|
|
|
|
{
|
2022-04-12 17:10:05 +02:00
|
|
|
|
var endQuoteIndex = commandPattern.IndexOf('\"', 1);
|
|
|
|
|
|
if (endQuoteIndex != -1)
|
|
|
|
|
|
{
|
|
|
|
|
|
Path = commandPattern.Substring(1, endQuoteIndex - 1);
|
|
|
|
|
|
ArgumentsPattern = commandPattern.Substring(endQuoteIndex + 1).Trim();
|
|
|
|
|
|
}
|
2022-01-25 20:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2022-04-12 17:10:05 +02:00
|
|
|
|
var spaceIndex = commandPattern.IndexOf(' ');
|
|
|
|
|
|
if (spaceIndex != -1)
|
|
|
|
|
|
{
|
|
|
|
|
|
Path = commandPattern.Substring(0, spaceIndex);
|
|
|
|
|
|
ArgumentsPattern = commandPattern.Substring(spaceIndex + 1).Trim();
|
|
|
|
|
|
}
|
2022-01-25 20:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-07 09:38:40 +02:00
|
|
|
|
// Packaged applications could be an URI. Example: shell:AppsFolder\Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe!App
|
|
|
|
|
|
if (!System.IO.Path.Exists(Path) && !Uri.TryCreate(Path, UriKind.Absolute, out _))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException(
|
|
|
|
|
|
$"Command validation failed: {commandPattern}",
|
|
|
|
|
|
nameof(commandPattern));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-25 20:31:57 +02:00
|
|
|
|
if (string.IsNullOrEmpty(Path))
|
|
|
|
|
|
{
|
2022-04-12 17:10:05 +02:00
|
|
|
|
throw new ArgumentOutOfRangeException(
|
|
|
|
|
|
nameof(Path),
|
|
|
|
|
|
"Default browser program path could not be determined.");
|
2022-01-25 20:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
2024-08-07 09:38:40 +02:00
|
|
|
|
// Fallback to MS Edge
|
2022-04-12 17:10:05 +02:00
|
|
|
|
Path = MSEdgePath;
|
|
|
|
|
|
Name = MSEdgeName;
|
|
|
|
|
|
ArgumentsPattern = MSEdgeArgumentsPattern;
|
2024-08-07 09:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
if (!_errorLogged)
|
|
|
|
|
|
{
|
|
|
|
|
|
Log.Exception("Exception when retrieving browser path/name. Path and Name are set to use Microsoft Edge.", e, typeof(DefaultBrowserInfo));
|
|
|
|
|
|
_errorLogged = true;
|
|
|
|
|
|
}
|
2022-01-25 20:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string GetRegistryValue(string registryLocation, string valueName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Microsoft.Win32.Registry.GetValue(registryLocation, valueName, null) as string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string GetIndirectString(string str)
|
|
|
|
|
|
{
|
|
|
|
|
|
var stringBuilder = new StringBuilder(128);
|
|
|
|
|
|
if (NativeMethods.SHLoadIndirectString(
|
|
|
|
|
|
str,
|
|
|
|
|
|
stringBuilder,
|
|
|
|
|
|
(uint)stringBuilder.Capacity,
|
|
|
|
|
|
IntPtr.Zero)
|
|
|
|
|
|
== HRESULT.S_OK)
|
|
|
|
|
|
{
|
|
|
|
|
|
return stringBuilder.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-12 17:10:05 +02:00
|
|
|
|
throw new ArgumentNullException(nameof(str), "Could not load indirect string.");
|
2022-01-25 20:31:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|