OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/app/chrome_exe_main_app_win.h" |
| 6 |
| 7 #include <memory> |
| 8 |
| 9 #include "base/win/scoped_co_mem.h" |
| 10 #include "chrome/installer/util/install_util.h" |
| 11 #include "chrome/installer/util/shell_util.h" |
| 12 |
| 13 // 2017/04/04: For this to work we also need |
| 14 // HKEY_CLASSES_ROOT\CLSID\{E65AECC7-DD9B-4D14-A4ED-73A5BEF1187A}\LocalServer32 |
| 15 // to be assigned command line to launch Chrome. |
| 16 |
| 17 #if 0 |
| 18 // #include <roapi.h> |
| 19 // #include <wchar.h> |
| 20 #include <NotificationActivationCallback.h> |
| 21 #include <propvarutil.h> |
| 22 #include <psapi.h> |
| 23 #include <shobjidl.h> |
| 24 #include <wrl.h> |
| 25 |
| 26 #include <cstring> |
| 27 #include <memory> |
| 28 #include <string> |
| 29 |
| 30 namespace mswr = Microsoft::WRL; |
| 31 // namespace mswrw = Microsoft::WRL::Wrappers; |
| 32 |
| 33 // namespace winapp = ABI::Windows::ApplicationModel; |
| 34 // namespace winxaml = ABI::Windows::UI::Xaml; |
| 35 // namespace winact = ABI::Windows::ApplicationModel::Activation; |
| 36 // namespace winfoundtn = ABI::Windows::Foundation; |
| 37 // namespace winui = ABI::Windows::UI; |
| 38 |
| 39 #else |
| 40 |
| 41 // #include <SDKDDKVer.h> |
| 42 #include <Windows.h> |
| 43 #include <Psapi.h> |
| 44 // #include <strsafe.h> |
| 45 #include <ShObjIdl.h> |
| 46 #include <Shlobj.h> |
| 47 #include <Pathcch.h> |
| 48 #include <propvarutil.h> |
| 49 #include <propkey.h> |
| 50 #include <wrl.h> |
| 51 #include <wrl\wrappers\corewrappers.h> |
| 52 #include <windows.ui.notifications.h> |
| 53 #include "NotificationActivationCallback.h" |
| 54 |
| 55 // using namespace ABI::Windows::Data::Xml::Dom; |
| 56 using namespace ABI::Windows::UI::Notifications; |
| 57 using namespace Microsoft::WRL; |
| 58 using namespace Microsoft::WRL::Wrappers; |
| 59 |
| 60 #endif |
| 61 |
| 62 namespace { |
| 63 |
| 64 base::string16 GetChromeAppId() { |
| 65 bool is_per_user_install = InstallUtil::IsPerUserInstall(); |
| 66 base::string16 appid = ShellUtil::GetBrowserModelId(is_per_user_install); |
| 67 DVLOG(1) << "Chrome Appid is " << appid.c_str(); |
| 68 return appid; |
| 69 } |
| 70 |
| 71 |
| 72 // Name: System.AppUserModel.ToastActivatorCLSID -- |
| 73 // PKEY_AppUserModel_ToastActivatorCLSID |
| 74 // Type: Guid -- VT_CLSID |
| 75 // FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 26 |
| 76 // |
| 77 // Used to CoCreate an INotificationActivationCallback interface to notify |
| 78 // about toast activations. |
| 79 EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY |
| 80 PKEY_AppUserModel_ToastActivatorCLSID = { |
| 81 {0x9F4C2855, |
| 82 0x9F79, |
| 83 0x4B39, |
| 84 {0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3}}, |
| 85 26}; |
| 86 |
| 87 struct CoTaskMemStringTraits { |
| 88 typedef PWSTR Type; |
| 89 |
| 90 inline static bool Close(_In_ Type h) throw() { |
| 91 ::CoTaskMemFree(h); |
| 92 return true; |
| 93 } |
| 94 |
| 95 inline static Type GetInvalidValue() throw() { |
| 96 return nullptr; |
| 97 } |
| 98 }; |
| 99 typedef HandleT<CoTaskMemStringTraits> CoTaskMemString; |
| 100 |
| 101 // For the app to be activated from Action Center, it needs to provide a COM |
| 102 // server to be called |
| 103 // when the notification is activated. The CLSID of the object needs to be |
| 104 // registered with the |
| 105 // OS via its shortcut so that it knows who to call later. |
| 106 class DECLSPEC_UUID("E65AECC7-DD9B-4D14-A4ED-73A5BEF1187A") |
| 107 NotificationActivator WrlSealed |
| 108 : public RuntimeClass<RuntimeClassFlags<ClassicCom>, |
| 109 INotificationActivationCallback> { |
| 110 public: |
| 111 HRESULT STDMETHODCALLTYPE Activate( |
| 112 _In_ LPCWSTR /*appUserModelId*/, |
| 113 _In_ LPCWSTR invokedArgs, |
| 114 /*_In_reads_(dataCount)*/ const NOTIFICATION_USER_INPUT_DATA* data, |
| 115 ULONG dataCount) override { |
| 116 ::MessageBoxW(NULL, invokedArgs, L"", MB_OK); |
| 117 return S_OK; |
| 118 } |
| 119 }; |
| 120 CoCreatableClass(NotificationActivator); |
| 121 |
| 122 // In order to display toasts, a desktop application must have a shortcut on the |
| 123 // Start menu. |
| 124 // Also, an AppUserModelID must be set on that shortcut. |
| 125 // |
| 126 // For the app to be activated from Action Center, it needs to register a COM |
| 127 // server with the OS |
| 128 // and register the CLSID of that COM server on the shortcut. |
| 129 // |
| 130 // The shortcut should be created as part of the installer. The following code |
| 131 // shows how to create |
| 132 // a shortcut and assign the AppUserModelID and ToastActivatorCLSID properties |
| 133 // using Windows APIs. |
| 134 // |
| 135 // Included in this project is a wxs file that be used with the WiX toolkit |
| 136 // to make an installer that creates the necessary shortcut. One or the other |
| 137 // should be used. |
| 138 // |
| 139 // This sample doesn't clean up the shortcut or COM registration. |
| 140 |
| 141 _Use_decl_annotations_ HRESULT |
| 142 InstallShortcut(PCWSTR shortcutPath, PCWSTR exePath, PCWSTR arguments) { |
| 143 MessageBoxW(NULL, L"InstallShortcut()", L"Title", MB_OK); |
| 144 ComPtr<IShellLink> shellLink; |
| 145 HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, |
| 146 IID_PPV_ARGS(&shellLink)); |
| 147 |
| 148 base::string16 app_id = GetChromeAppId(); |
| 149 |
| 150 if (SUCCEEDED(hr)) { |
| 151 hr = shellLink->SetPath(exePath); |
| 152 |
| 153 if (SUCCEEDED(hr)) { |
| 154 hr = shellLink->SetArguments(arguments); |
| 155 |
| 156 if (SUCCEEDED(hr)) { |
| 157 ComPtr<IPropertyStore> propertyStore; |
| 158 |
| 159 hr = shellLink.As(&propertyStore); |
| 160 if (SUCCEEDED(hr)) { |
| 161 PROPVARIANT propVar; |
| 162 propVar.vt = VT_LPWSTR; |
| 163 propVar.pwszVal = const_cast<PWSTR>( |
| 164 app_id.c_str()); // for _In_ scenarios, we don't need a copy |
| 165 hr = propertyStore->SetValue(PKEY_AppUserModel_ID, propVar); |
| 166 if (SUCCEEDED(hr)) { |
| 167 propVar.vt = VT_CLSID; |
| 168 propVar.puuid = |
| 169 const_cast<CLSID*>(&__uuidof(NotificationActivator)); |
| 170 hr = propertyStore->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, |
| 171 propVar); |
| 172 if (SUCCEEDED(hr)) { |
| 173 hr = propertyStore->Commit(); |
| 174 if (SUCCEEDED(hr)) { |
| 175 ComPtr<IPersistFile> persistFile; |
| 176 hr = shellLink.As(&persistFile); |
| 177 if (SUCCEEDED(hr)) { |
| 178 hr = persistFile->Save(shortcutPath, TRUE); |
| 179 } |
| 180 } |
| 181 } |
| 182 } |
| 183 } |
| 184 } |
| 185 } |
| 186 } |
| 187 if (!SUCCEEDED(hr)) { |
| 188 MessageBoxA(NULL, "InstallShortcut() failed.", "Title", MB_OK); |
| 189 } |
| 190 return hr; |
| 191 } |
| 192 |
| 193 _Use_decl_annotations_ HRESULT |
| 194 RegisterComServer(PCWSTR exePath) { |
| 195 // We don't need to worry about overflow here as ::GetModuleFileName won't |
| 196 // return anything bigger than the max file system path (much fewer than max |
| 197 // of DWORD). |
| 198 DWORD dataSize = static_cast<DWORD>((::wcslen(exePath) + 1) * sizeof(WCHAR)); |
| 199 |
| 200 // In this sample, the app UI is registered to launch when the COM callback is |
| 201 // needed. |
| 202 // Other options might be to launch a background process instead that then |
| 203 // decides to launch |
| 204 // the UI if needed by that particular notification. |
| 205 return HRESULT_FROM_WIN32(::RegSetKeyValue( |
| 206 HKEY_CURRENT_USER, |
| 207 LR"(SOFTWARE\Classes\CLSID\{E65AECC7-DD9B-4D14-A4ED-73A5BEF1187A}" |
| 208 LR"\LocalServer32)", |
| 209 nullptr, REG_SZ, reinterpret_cast<const BYTE*>(exePath), dataSize)); |
| 210 } |
| 211 |
| 212 HRESULT RegisterAppForNotificationSupport() { |
| 213 CoTaskMemString appData; |
| 214 |
| 215 auto hr = ::SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, |
| 216 appData.GetAddressOf()); |
| 217 if (SUCCEEDED(hr)) { |
| 218 wchar_t shortcutPath[MAX_PATH]; |
| 219 hr = ::PathCchCombine( |
| 220 shortcutPath, ARRAYSIZE(shortcutPath), appData.Get(), |
| 221 LR"(Microsoft\Windows\Start Menu\Programs\Tester\tester.lnk)"); |
| 222 if (SUCCEEDED(hr)) { |
| 223 // MessageBoxW(NULL, shortcutPath, L"Title", MB_OK); |
| 224 DWORD attributes = ::GetFileAttributes(shortcutPath); |
| 225 bool fileExists = attributes < 0xFFFFFFF; |
| 226 if (!fileExists) { |
| 227 wchar_t exePath[MAX_PATH]; |
| 228 DWORD charWritten = |
| 229 ::GetModuleFileName(nullptr, exePath, ARRAYSIZE(exePath)); |
| 230 MessageBoxW(NULL, exePath, L"Title", MB_OK); |
| 231 hr = charWritten > 0 ? S_OK : HRESULT_FROM_WIN32(::GetLastError()); |
| 232 if (SUCCEEDED(hr)) { |
| 233 const wchar_t* arguments = L"--enable-features=NativeNotifications " |
| 234 L"--user-data-dir=R:\\p"; |
| 235 hr = InstallShortcut(shortcutPath, exePath, arguments); |
| 236 if (SUCCEEDED(hr)) { |
| 237 hr = RegisterComServer(exePath); |
| 238 } |
| 239 } |
| 240 } |
| 241 } |
| 242 } |
| 243 if (!SUCCEEDED(hr)) { |
| 244 MessageBoxA(NULL, "RegisterAppForNotificationSupport() failed.", "Title", |
| 245 MB_OK); |
| 246 } |
| 247 return hr; |
| 248 } |
| 249 |
| 250 // Register activator for notifications |
| 251 HRESULT RegisterActivator() { |
| 252 // Module<OutOfProc> needs a callback registered before it can be used. |
| 253 // Since we don't care about when it shuts down, we'll pass an empty lambda |
| 254 // here. |
| 255 Module<OutOfProc>::Create([] {}); |
| 256 |
| 257 // If a local server process only hosts the COM object then COM expects |
| 258 // the COM server host to shutdown when the references drop to zero. |
| 259 // Since the user might still be using the program after activating the |
| 260 // notification, |
| 261 // we don't want to shutdown immediately. Incrementing the object count tells |
| 262 // COM that |
| 263 // we aren't done yet. |
| 264 Module<OutOfProc>::GetModule().IncrementObjectCount(); |
| 265 |
| 266 return Module<OutOfProc>::GetModule().RegisterObjects(); |
| 267 } |
| 268 |
| 269 // Unregister our activator COM object |
| 270 void UnregisterActivator() { |
| 271 Module<OutOfProc>::GetModule().UnregisterObjects(); |
| 272 |
| 273 Module<OutOfProc>::GetModule().DecrementObjectCount(); |
| 274 } |
| 275 |
| 276 class HackyRegister { |
| 277 public: |
| 278 HackyRegister() { |
| 279 } |
| 280 HRESULT Run() { |
| 281 HRESULT hr = RegisterActivator(); |
| 282 if (SUCCEEDED(hr)) |
| 283 has_reg = true; |
| 284 return hr; |
| 285 } |
| 286 ~HackyRegister() { |
| 287 if (has_reg) |
| 288 UnregisterActivator(); |
| 289 } |
| 290 private: |
| 291 bool has_reg = false; |
| 292 }; |
| 293 |
| 294 std::unique_ptr<HackyRegister> my_reg; |
| 295 |
| 296 } // namespace |
| 297 |
| 298 void PrepareChromeForWindows10() { |
| 299 RoInitializeWrapper winRtInitializer(RO_INIT_MULTITHREADED); |
| 300 |
| 301 // Experimental code: Attempt to respond to notificaiton events, using idea |
| 302 // from https://github.com/WindowsNotifications/desktop-toasts |
| 303 HRESULT hr = winRtInitializer; |
| 304 if (!SUCCEEDED(hr)) |
| 305 return; |
| 306 |
| 307 hr = RegisterAppForNotificationSupport(); |
| 308 if (SUCCEEDED(hr)) { |
| 309 my_reg.reset(new HackyRegister()); |
| 310 hr = my_reg->Run(); |
| 311 if (SUCCEEDED(hr)) { |
| 312 // ::MessageBoxA(NULL, "Registered", "Title", MB_OK); |
| 313 } |
| 314 } |
| 315 } |
OLD | NEW |