329 lines
10 KiB
C++
329 lines
10 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* This file exists so that LaunchModernSettingsDialogDefaultApps can be called
|
|
* without linking to libxul.
|
|
*/
|
|
#include "WindowsDefaultBrowser.h"
|
|
|
|
#include "city.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/WindowsVersion.h"
|
|
#include "mozilla/WinHeaderOnlyUtils.h"
|
|
|
|
// This must be before any other includes that might include shlobj.h
|
|
#define INITGUID
|
|
#include <shlobj.h>
|
|
|
|
#include <lm.h>
|
|
#include <shellapi.h>
|
|
#include <shlwapi.h>
|
|
#include <uiautomation.h>
|
|
#include <wchar.h>
|
|
#include <windows.h>
|
|
|
|
#define APP_REG_NAME_BASE L"Firefox-"
|
|
|
|
static bool IsWindowsLogonConnected() {
|
|
WCHAR userName[UNLEN + 1];
|
|
DWORD size = std::size(userName);
|
|
if (!GetUserNameW(userName, &size)) {
|
|
return false;
|
|
}
|
|
|
|
LPUSER_INFO_24 info;
|
|
if (NetUserGetInfo(nullptr, userName, 24, (LPBYTE*)&info) != NERR_Success) {
|
|
return false;
|
|
}
|
|
bool connected = info->usri24_internet_identity;
|
|
NetApiBufferFree(info);
|
|
|
|
return connected;
|
|
}
|
|
|
|
static bool SettingsAppBelievesConnected() {
|
|
const wchar_t* keyPath = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations";
|
|
const wchar_t* valueName = L"IsConnectedAtLogon";
|
|
|
|
uint32_t value = 0;
|
|
DWORD size = sizeof(uint32_t);
|
|
LSTATUS ls = RegGetValueW(HKEY_CURRENT_USER, keyPath, valueName, RRF_RT_ANY,
|
|
nullptr, &value, &size);
|
|
if (ls != ERROR_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
return !!value;
|
|
}
|
|
|
|
bool GetAppRegName(mozilla::UniquePtr<wchar_t[]>& aAppRegName) {
|
|
mozilla::UniquePtr<wchar_t[]> appDirStr;
|
|
bool success = mozilla::GetInstallDirectory(appDirStr);
|
|
if (!success) {
|
|
return success;
|
|
}
|
|
|
|
uint64_t hash = CityHash64(reinterpret_cast<char*>(appDirStr.get()),
|
|
wcslen(appDirStr.get()) * sizeof(wchar_t));
|
|
|
|
const wchar_t* format = L"%s%I64X";
|
|
int bufferSize = _scwprintf(format, APP_REG_NAME_BASE, hash);
|
|
++bufferSize; // Extra byte for terminating null
|
|
aAppRegName = mozilla::MakeUnique<wchar_t[]>(bufferSize);
|
|
|
|
_snwprintf_s(aAppRegName.get(), bufferSize, _TRUNCATE, format,
|
|
APP_REG_NAME_BASE, hash);
|
|
|
|
return success;
|
|
}
|
|
|
|
bool LaunchControlPanelDefaultPrograms() {
|
|
// Build the path control.exe path safely
|
|
WCHAR controlEXEPath[MAX_PATH + 1] = {'\0'};
|
|
if (!GetSystemDirectoryW(controlEXEPath, MAX_PATH)) {
|
|
return false;
|
|
}
|
|
LPCWSTR controlEXE = L"control.exe";
|
|
if (wcslen(controlEXEPath) + wcslen(controlEXE) >= MAX_PATH) {
|
|
return false;
|
|
}
|
|
if (!PathAppendW(controlEXEPath, controlEXE)) {
|
|
return false;
|
|
}
|
|
|
|
const wchar_t* paramFormat =
|
|
L"control.exe /name Microsoft.DefaultPrograms "
|
|
L"/page pageDefaultProgram\\pageAdvancedSettings?pszAppName=%s";
|
|
mozilla::UniquePtr<wchar_t[]> appRegName;
|
|
GetAppRegName(appRegName);
|
|
int bufferSize = _scwprintf(paramFormat, appRegName.get());
|
|
++bufferSize; // Extra byte for terminating null
|
|
mozilla::UniquePtr<wchar_t[]> params =
|
|
mozilla::MakeUnique<wchar_t[]>(bufferSize);
|
|
_snwprintf_s(params.get(), bufferSize, _TRUNCATE, paramFormat,
|
|
appRegName.get());
|
|
|
|
STARTUPINFOW si = {sizeof(si), 0};
|
|
si.dwFlags = STARTF_USESHOWWINDOW;
|
|
si.wShowWindow = SW_SHOWDEFAULT;
|
|
PROCESS_INFORMATION pi = {0};
|
|
if (!CreateProcessW(controlEXEPath, params.get(), nullptr, nullptr, FALSE, 0,
|
|
nullptr, nullptr, &si, &pi)) {
|
|
return false;
|
|
}
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool IsAppRegistered(HKEY rootKey, const wchar_t* appRegName) {
|
|
const wchar_t* keyPath = L"Software\\RegisteredApplications";
|
|
|
|
DWORD size = sizeof(uint32_t);
|
|
LSTATUS ls = RegGetValueW(rootKey, keyPath, appRegName, RRF_RT_ANY, nullptr,
|
|
nullptr, &size);
|
|
return ls == ERROR_SUCCESS;
|
|
}
|
|
|
|
static bool LaunchMsSettingsProtocol() {
|
|
mozilla::UniquePtr<wchar_t[]> params = nullptr;
|
|
if (mozilla::HasPackageIdentity()) {
|
|
mozilla::UniquePtr<wchar_t[]> packageFamilyName =
|
|
mozilla::GetPackageFamilyName();
|
|
if (packageFamilyName) {
|
|
const wchar_t* paramFormat =
|
|
L"ms-settings:defaultapps?registeredAUMID=%s!App";
|
|
int bufferSize = _scwprintf(paramFormat, packageFamilyName.get());
|
|
++bufferSize; // Extra byte for terminating null
|
|
params = mozilla::MakeUnique<wchar_t[]>(bufferSize);
|
|
_snwprintf_s(params.get(), bufferSize, _TRUNCATE, paramFormat,
|
|
packageFamilyName.get());
|
|
}
|
|
}
|
|
if (!params) {
|
|
mozilla::UniquePtr<wchar_t[]> appRegName;
|
|
GetAppRegName(appRegName);
|
|
const wchar_t* paramFormat =
|
|
IsAppRegistered(HKEY_CURRENT_USER, appRegName.get()) ||
|
|
!IsAppRegistered(HKEY_LOCAL_MACHINE, appRegName.get())
|
|
? L"ms-settings:defaultapps?registeredAppUser=%s"
|
|
: L"ms-settings:defaultapps?registeredAppMachine=%s";
|
|
int bufferSize = _scwprintf(paramFormat, appRegName.get());
|
|
++bufferSize; // Extra byte for terminating null
|
|
params = mozilla::MakeUnique<wchar_t[]>(bufferSize);
|
|
_snwprintf_s(params.get(), bufferSize, _TRUNCATE, paramFormat,
|
|
appRegName.get());
|
|
}
|
|
|
|
SHELLEXECUTEINFOW seinfo = {sizeof(seinfo)};
|
|
seinfo.lpFile = params.get();
|
|
seinfo.nShow = SW_SHOWNORMAL;
|
|
return ShellExecuteExW(&seinfo);
|
|
}
|
|
|
|
bool LaunchModernSettingsDialogDefaultApps() {
|
|
if (mozilla::IsWin11OrLater()) {
|
|
return LaunchMsSettingsProtocol();
|
|
}
|
|
|
|
if (!mozilla::IsWindows10BuildOrLater(14965) && !IsWindowsLogonConnected() &&
|
|
SettingsAppBelievesConnected()) {
|
|
// Use the classic Control Panel to work around a bug of older
|
|
// builds of Windows 10.
|
|
return LaunchControlPanelDefaultPrograms();
|
|
}
|
|
|
|
IApplicationActivationManager* pActivator;
|
|
HRESULT hr = CoCreateInstance(
|
|
CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC,
|
|
IID_IApplicationActivationManager, (void**)&pActivator);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
DWORD pid;
|
|
hr = pActivator->ActivateApplication(
|
|
L"windows.immersivecontrolpanel_cw5n1h2txyewy"
|
|
L"!microsoft.windows.immersivecontrolpanel",
|
|
L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
|
|
if (SUCCEEDED(hr)) {
|
|
// Do not check error because we could at least open
|
|
// the "Default apps" setting.
|
|
pActivator->ActivateApplication(
|
|
L"windows.immersivecontrolpanel_cw5n1h2txyewy"
|
|
L"!microsoft.windows.immersivecontrolpanel",
|
|
L"page=SettingsPageAppsDefaults"
|
|
L"&target=SystemSettings_DefaultApps_Browser",
|
|
AO_NONE, &pid);
|
|
}
|
|
pActivator->Release();
|
|
return SUCCEEDED(hr);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static already_AddRefed<IUIAutomationElement> TryFindElementByAutomationId(
|
|
HWND aWindow, LPCWSTR aId) {
|
|
RefPtr<IUIAutomation> automation;
|
|
HRESULT hr{CoCreateInstance(CLSID_CUIAutomation, nullptr,
|
|
CLSCTX_INPROC_SERVER, IID_IUIAutomation,
|
|
getter_AddRefs(automation))};
|
|
if (FAILED(hr)) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<IUIAutomationElement> window;
|
|
hr = automation->ElementFromHandle(aWindow, getter_AddRefs(window));
|
|
if (FAILED(hr)) {
|
|
return nullptr;
|
|
}
|
|
|
|
VARIANT value{};
|
|
value.vt = VT_BSTR;
|
|
value.bstrVal = SysAllocString(aId);
|
|
RefPtr<IUIAutomationCondition> propertyCondition;
|
|
hr = automation->CreatePropertyCondition(UIA_AutomationIdPropertyId, value,
|
|
getter_AddRefs(propertyCondition));
|
|
SysFreeString(value.bstrVal);
|
|
if (FAILED(hr)) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<IUIAutomationElement> element;
|
|
hr = window->FindFirst(TreeScope_Descendants, propertyCondition,
|
|
getter_AddRefs(element));
|
|
if (SUCCEEDED(hr) && element) {
|
|
return element.forget();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static bool PathHasFilename(LPCWSTR aPath, LPCWSTR aFilename) {
|
|
LPCWSTR fileName{::wcsrchr(aPath, L'\\')};
|
|
fileName = fileName ? fileName + 1 : aPath;
|
|
return ::_wcsicmp(fileName, aFilename) == 0;
|
|
}
|
|
|
|
static bool IsSystemSettingsProcessByPid(DWORD aPid) {
|
|
nsAutoHandle process{
|
|
::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, aPid)};
|
|
if (!process) {
|
|
return false;
|
|
}
|
|
|
|
DWORD bufferLength{MAX_PATH};
|
|
auto buffer{mozilla::MakeUnique<wchar_t[]>(bufferLength)};
|
|
if (::QueryFullProcessImageNameW(process, 0, buffer.get(), &bufferLength)) {
|
|
LPCWSTR kProcessName{L"SystemSettings.exe"};
|
|
return PathHasFilename(buffer.get(), kProcessName);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool IsSystemSettingsProcessByHandle(HWND aHwnd) {
|
|
DWORD pid{0};
|
|
::GetWindowThreadProcessId(aHwnd, &pid);
|
|
return IsSystemSettingsProcessByPid(pid);
|
|
}
|
|
|
|
static BOOL CALLBACK FindSystemSettingsChildProc(HWND aHwnd, LPARAM aLParam) {
|
|
if (IsSystemSettingsProcessByHandle(aHwnd)) {
|
|
*reinterpret_cast<bool*>(aLParam) = true;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static bool IsApplicationFrameWindow(HWND aHwnd) {
|
|
WCHAR className[MAX_PATH]{};
|
|
::GetClassNameW(aHwnd, className, std::size(className));
|
|
LPCWSTR kClassName{L"ApplicationFrameWindow"};
|
|
return (wcscmp(className, kClassName) == 0);
|
|
}
|
|
|
|
static BOOL CALLBACK FindSystemSettingsProc(HWND aHwnd, LPARAM aLParam) {
|
|
// SystemSettings is hosted inside ApplicationFrameHost, so we first find
|
|
// the ApplicationFrameWindow and then check if SystemSettings is direct child
|
|
if (IsApplicationFrameWindow(aHwnd)) {
|
|
bool hasChild{false};
|
|
::EnumChildWindows(aHwnd, FindSystemSettingsChildProc,
|
|
reinterpret_cast<LPARAM>(&hasChild));
|
|
if (hasChild) {
|
|
*reinterpret_cast<HWND*>(aLParam) = aHwnd;
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static HWND FindSystemSettingsWindow() {
|
|
HWND hwnd{nullptr};
|
|
::EnumWindows(FindSystemSettingsProc, reinterpret_cast<LPARAM>(&hwnd));
|
|
return hwnd;
|
|
}
|
|
|
|
static UIWindowElement FindSetDefaultBrowserButtonByAutomationId(LPCWSTR aId) {
|
|
if (HWND window{FindSystemSettingsWindow()}) {
|
|
if (UIElement element{TryFindElementByAutomationId(window, aId)}) {
|
|
return {window, element};
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static LPCWSTR GetSetDefaultBrowserButtonAutomationId() {
|
|
// clang-format off
|
|
LPCWSTR kWin11AutomationId{L"SystemSettings_DefaultApps_DefaultBrowserWithPinToStartAndTaskbarAction_Button"};
|
|
LPCWSTR kWin10AutomationId{L"SystemSettings_DefaultApps_Browser_Button"};
|
|
// clang-format on
|
|
return mozilla::IsWin11OrLater() ? kWin11AutomationId : kWin10AutomationId;
|
|
}
|
|
|
|
[[nodiscard]] UIWindowElement FindSetDefaultBrowserButton() {
|
|
LPCWSTR id{GetSetDefaultBrowserButtonAutomationId()};
|
|
return FindSetDefaultBrowserButtonByAutomationId(id);
|
|
}
|