File: default_apps_util.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (213 lines) | stat: -rw-r--r-- 7,963 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/win/default_apps_util.h"

#include <shobjidl.h>

#include <shellapi.h>
#include <wrl/client.h>

#include <optional>
#include <string_view>

#include "base/metrics/histogram_functions.h"
#include "base/strings/cstring_view.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/com_init_util.h"
#include "base/win/registry.h"
#include "base/win/windows_version.h"

namespace {

// Undocumented COM interface for opening the "set default app for <file type>"
// dialog.
class __declspec(uuid("6A283FE2-ECFA-4599-91C4-E80957137B26")) IOpenWithLauncher
    : public IUnknown {
 public:
  virtual HRESULT STDMETHODCALLTYPE Launch(HWND hWndParent,
                                           const wchar_t* lpszPath,
                                           int flags) = 0;
};

// Returns the class ID for the "Execute Unknown" class, read from
// `HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\OpenWith`.
// Returns std::nullopt upon failure.
std::optional<CLSID> GetOpenWithLauncherCLSID() {
  std::wstring value;
  base::win::RegKey(HKEY_LOCAL_MACHINE,
                    L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OpenWith",
                    KEY_QUERY_VALUE)
      .ReadValue(L"OpenWithLauncher", &value);
  if (value.empty()) {
    return std::nullopt;
  }
  CLSID clsid;
  const auto hr = ::CLSIDFromString(value.c_str(), &clsid);
  return SUCCEEDED(hr) ? std::make_optional(clsid) : std::nullopt;
}

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. These represent various outcomes of
// attempting to open the Settings app via the IOpenWithLauncher COM interface.
enum class OpenWithLauncherResult {
  // The settings window was launched successfully and the user changed a
  // setting.
  kSuccess = 0,
  // The settings window was launched successfully, but the user closed it
  // without taking action.
  kSuccessNoChange = 1,
  // Failed to get the class ID from the registry.
  kClsidNotFound = 2,
  // Failed to create an instance of the COM class.
  kComError = 3,
  // Launching the Settings app failed.
  kLaunchError = 4,
  kMaxValue = kLaunchError
};

// Records the `result` of opening the Settings app via the IOpenWithLauncher
// COM interface.
void RecordOpenWithLauncherResult(OpenWithLauncherResult result) {
  base::UmaHistogramEnumeration("Windows.OpenWithLauncherResult", result);
}

// Returns the target used as a activate parameter when opening the settings
// pointing to the page that is the most relevant to a user trying to change the
// default handler for `protocol`.
std::wstring GetTargetForDefaultAppsSettings(std::wstring_view protocol) {
  static constexpr std::wstring_view kSystemSettingsDefaultAppsPrefix(
      L"SystemSettings_DefaultApps_");
  if (base::EqualsCaseInsensitiveASCII(protocol, L"http")) {
    return base::StrCat({kSystemSettingsDefaultAppsPrefix, L"Browser"});
  }
  if (base::EqualsCaseInsensitiveASCII(protocol, L"mailto")) {
    return base::StrCat({kSystemSettingsDefaultAppsPrefix, L"Email"});
  }
  return L"SettingsPageAppsDefaultsProtocolView";
}

}  // namespace

namespace base::win {

bool LaunchDefaultAppsSettingsModernDialog(std::wstring_view protocol) {
  // The appModelId looks arbitrary but it is the same in Win8 and Win10. There
  // is no easy way to retrieve the appModelId from the registry.
  static constexpr wchar_t kControlPanelAppModelId[] =
      L"windows.immersivecontrolpanel_cw5n1h2txyewy"
      L"!microsoft.windows.immersivecontrolpanel";

  Microsoft::WRL::ComPtr<IApplicationActivationManager> activator;
  HRESULT hr =
      ::CoCreateInstance(CLSID_ApplicationActivationManager, nullptr,
                         CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&activator));
  if (FAILED(hr)) {
    return false;
  }

  DWORD pid = 0;
  hr = activator->ActivateApplication(
      kControlPanelAppModelId, L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
  if (FAILED(hr)) {
    return false;
  }
  // Scrolling to a specific protocol is only possible on Windows 10.
  if (protocol.empty() || GetVersion() >= Version::WIN11) {
    return true;
  }

  hr = activator->ActivateApplication(
      kControlPanelAppModelId,
      base::StrCat({L"page=SettingsPageAppsDefaults&target=",
                    GetTargetForDefaultAppsSettings(protocol)})
          .c_str(),
      AO_NONE, &pid);
  return SUCCEEDED(hr);
}

bool LaunchDefaultAppForFileExtensionSettings(
    base::wcstring_view file_extension,
    HWND parent_hwnd) {
  AssertComInitialized();

  // Create an "Execute Unknown" COM object with `IOpenWithLauncher` interface.
  const auto open_with_launcher_clsid = GetOpenWithLauncherCLSID();
  if (!open_with_launcher_clsid) {
    RecordOpenWithLauncherResult(OpenWithLauncherResult::kClsidNotFound);
    return false;
  }
  Microsoft::WRL::ComPtr<IOpenWithLauncher> open_with_launcher;
  if (FAILED(::CoCreateInstance(*open_with_launcher_clsid, nullptr,
                                CLSCTX_LOCAL_SERVER,
                                IID_PPV_ARGS(&open_with_launcher)))) {
    RecordOpenWithLauncherResult(OpenWithLauncherResult::kComError);
    return false;
  }

  // Open "select a default app for `file_extension` files" dialog.
  // `kOpenWithFlags` is a working `flags` argument discovered by observation.
  static constexpr int kOpenWithFlags = 0x2004;
  const HRESULT hr = open_with_launcher->Launch(
      parent_hwnd, file_extension.data(), kOpenWithFlags);
  if (SUCCEEDED(hr)) {
    RecordOpenWithLauncherResult(OpenWithLauncherResult::kSuccess);
    return true;
  }
  // On Windows 10, `ERROR_CANCELLED` just means the user closed the dialog
  // without changing anything.
  if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
    RecordOpenWithLauncherResult(OpenWithLauncherResult::kSuccessNoChange);
    return true;
  }
  RecordOpenWithLauncherResult(OpenWithLauncherResult::kLaunchError);
  return false;
}

bool LaunchSettingsDefaultApps(std::wstring_view app_name,
                               bool is_per_user_install) {
  AssertComInitialized();

  // The `app_name` parameter is escaped using URL escaping, not because it is
  // part of a URL, but because the Settings app parses the parameter as if it
  // was part of a URL.
  // The '+' character is not escaped by `EscapeQueryParamValue`, because it is
  // valid in a query parameter, but it is not valid in this context, so it
  // needs to be escaped.
  const std::wstring settings_url = StrCat(
      {L"ms-settings:defaultapps?",
       is_per_user_install ? L"registeredAppUser=" : L"registeredAppMachine=",
       ASCIIToWide(EscapeQueryParamValue(WideToUTF8(app_name),
                                         /*use_plus=*/false))});
  return reinterpret_cast<intptr_t>(::ShellExecute(
             /*hwnd=*/nullptr, L"open", settings_url.c_str(),
             /*lpParameters=*/nullptr,
             /*lpDirectory=*/nullptr, SW_SHOWNORMAL)) > 32;
}

bool LaunchSettingsUri(base::wcstring_view uri) {
  AssertComInitialized();

  Microsoft::WRL::ComPtr<IApplicationActivationManager> activator;
  HRESULT hr = ::CoCreateInstance(CLSID_ApplicationActivationManager, nullptr,
                                  CLSCTX_ALL, IID_PPV_ARGS(&activator));
  if (FAILED(hr)) {
    return false;
  }

  ::CoAllowSetForegroundWindow(activator.Get(), nullptr);

  static constexpr wchar_t kControlPanelAppModelId[] =
      L"windows.immersivecontrolpanel_cw5n1h2txyewy"
      L"!microsoft.windows.immersivecontrolpanel";
  DWORD pid = 0;
  hr = activator->ActivateApplication(kControlPanelAppModelId, uri.c_str(),
                                      AO_NONE, &pid);
  return SUCCEEDED(hr);
}

}  // namespace base::win