Index: chrome/app/chrome_exe_main_app_win.cc |
diff --git a/chrome/app/chrome_exe_main_app_win.cc b/chrome/app/chrome_exe_main_app_win.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..25d33e97885bdf2e0f8d9af527dfe290f6f7e76c |
--- /dev/null |
+++ b/chrome/app/chrome_exe_main_app_win.cc |
@@ -0,0 +1,315 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/app/chrome_exe_main_app_win.h" |
+ |
+#include <memory> |
+ |
+#include "base/win/scoped_co_mem.h" |
+#include "chrome/installer/util/install_util.h" |
+#include "chrome/installer/util/shell_util.h" |
+ |
+// 2017/04/04: For this to work we also need |
+// HKEY_CLASSES_ROOT\CLSID\{E65AECC7-DD9B-4D14-A4ED-73A5BEF1187A}\LocalServer32 |
+// to be assigned command line to launch Chrome. |
+ |
+#if 0 |
+// #include <roapi.h> |
+// #include <wchar.h> |
+#include <NotificationActivationCallback.h> |
+#include <propvarutil.h> |
+#include <psapi.h> |
+#include <shobjidl.h> |
+#include <wrl.h> |
+ |
+#include <cstring> |
+#include <memory> |
+#include <string> |
+ |
+namespace mswr = Microsoft::WRL; |
+// namespace mswrw = Microsoft::WRL::Wrappers; |
+ |
+// namespace winapp = ABI::Windows::ApplicationModel; |
+// namespace winxaml = ABI::Windows::UI::Xaml; |
+// namespace winact = ABI::Windows::ApplicationModel::Activation; |
+// namespace winfoundtn = ABI::Windows::Foundation; |
+// namespace winui = ABI::Windows::UI; |
+ |
+#else |
+ |
+// #include <SDKDDKVer.h> |
+#include <Windows.h> |
+#include <Psapi.h> |
+// #include <strsafe.h> |
+#include <ShObjIdl.h> |
+#include <Shlobj.h> |
+#include <Pathcch.h> |
+#include <propvarutil.h> |
+#include <propkey.h> |
+#include <wrl.h> |
+#include <wrl\wrappers\corewrappers.h> |
+#include <windows.ui.notifications.h> |
+#include "NotificationActivationCallback.h" |
+ |
+// using namespace ABI::Windows::Data::Xml::Dom; |
+using namespace ABI::Windows::UI::Notifications; |
+using namespace Microsoft::WRL; |
+using namespace Microsoft::WRL::Wrappers; |
+ |
+#endif |
+ |
+namespace { |
+ |
+base::string16 GetChromeAppId() { |
+ bool is_per_user_install = InstallUtil::IsPerUserInstall(); |
+ base::string16 appid = ShellUtil::GetBrowserModelId(is_per_user_install); |
+ DVLOG(1) << "Chrome Appid is " << appid.c_str(); |
+ return appid; |
+} |
+ |
+ |
+// Name: System.AppUserModel.ToastActivatorCLSID -- |
+// PKEY_AppUserModel_ToastActivatorCLSID |
+// Type: Guid -- VT_CLSID |
+// FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 26 |
+// |
+// Used to CoCreate an INotificationActivationCallback interface to notify |
+// about toast activations. |
+EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY |
+ PKEY_AppUserModel_ToastActivatorCLSID = { |
+ {0x9F4C2855, |
+ 0x9F79, |
+ 0x4B39, |
+ {0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3}}, |
+ 26}; |
+ |
+struct CoTaskMemStringTraits { |
+ typedef PWSTR Type; |
+ |
+ inline static bool Close(_In_ Type h) throw() { |
+ ::CoTaskMemFree(h); |
+ return true; |
+ } |
+ |
+ inline static Type GetInvalidValue() throw() { |
+ return nullptr; |
+ } |
+}; |
+typedef HandleT<CoTaskMemStringTraits> CoTaskMemString; |
+ |
+// For the app to be activated from Action Center, it needs to provide a COM |
+// server to be called |
+// when the notification is activated. The CLSID of the object needs to be |
+// registered with the |
+// OS via its shortcut so that it knows who to call later. |
+class DECLSPEC_UUID("E65AECC7-DD9B-4D14-A4ED-73A5BEF1187A") |
+ NotificationActivator WrlSealed |
+ : public RuntimeClass<RuntimeClassFlags<ClassicCom>, |
+ INotificationActivationCallback> { |
+ public: |
+ HRESULT STDMETHODCALLTYPE Activate( |
+ _In_ LPCWSTR /*appUserModelId*/, |
+ _In_ LPCWSTR invokedArgs, |
+ /*_In_reads_(dataCount)*/ const NOTIFICATION_USER_INPUT_DATA* data, |
+ ULONG dataCount) override { |
+ ::MessageBoxW(NULL, invokedArgs, L"", MB_OK); |
+ return S_OK; |
+ } |
+}; |
+CoCreatableClass(NotificationActivator); |
+ |
+// In order to display toasts, a desktop application must have a shortcut on the |
+// Start menu. |
+// Also, an AppUserModelID must be set on that shortcut. |
+// |
+// For the app to be activated from Action Center, it needs to register a COM |
+// server with the OS |
+// and register the CLSID of that COM server on the shortcut. |
+// |
+// The shortcut should be created as part of the installer. The following code |
+// shows how to create |
+// a shortcut and assign the AppUserModelID and ToastActivatorCLSID properties |
+// using Windows APIs. |
+// |
+// Included in this project is a wxs file that be used with the WiX toolkit |
+// to make an installer that creates the necessary shortcut. One or the other |
+// should be used. |
+// |
+// This sample doesn't clean up the shortcut or COM registration. |
+ |
+_Use_decl_annotations_ HRESULT |
+InstallShortcut(PCWSTR shortcutPath, PCWSTR exePath, PCWSTR arguments) { |
+ MessageBoxW(NULL, L"InstallShortcut()", L"Title", MB_OK); |
+ ComPtr<IShellLink> shellLink; |
+ HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, |
+ IID_PPV_ARGS(&shellLink)); |
+ |
+ base::string16 app_id = GetChromeAppId(); |
+ |
+ if (SUCCEEDED(hr)) { |
+ hr = shellLink->SetPath(exePath); |
+ |
+ if (SUCCEEDED(hr)) { |
+ hr = shellLink->SetArguments(arguments); |
+ |
+ if (SUCCEEDED(hr)) { |
+ ComPtr<IPropertyStore> propertyStore; |
+ |
+ hr = shellLink.As(&propertyStore); |
+ if (SUCCEEDED(hr)) { |
+ PROPVARIANT propVar; |
+ propVar.vt = VT_LPWSTR; |
+ propVar.pwszVal = const_cast<PWSTR>( |
+ app_id.c_str()); // for _In_ scenarios, we don't need a copy |
+ hr = propertyStore->SetValue(PKEY_AppUserModel_ID, propVar); |
+ if (SUCCEEDED(hr)) { |
+ propVar.vt = VT_CLSID; |
+ propVar.puuid = |
+ const_cast<CLSID*>(&__uuidof(NotificationActivator)); |
+ hr = propertyStore->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, |
+ propVar); |
+ if (SUCCEEDED(hr)) { |
+ hr = propertyStore->Commit(); |
+ if (SUCCEEDED(hr)) { |
+ ComPtr<IPersistFile> persistFile; |
+ hr = shellLink.As(&persistFile); |
+ if (SUCCEEDED(hr)) { |
+ hr = persistFile->Save(shortcutPath, TRUE); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } |
+ if (!SUCCEEDED(hr)) { |
+ MessageBoxA(NULL, "InstallShortcut() failed.", "Title", MB_OK); |
+ } |
+ return hr; |
+} |
+ |
+_Use_decl_annotations_ HRESULT |
+RegisterComServer(PCWSTR exePath) { |
+ // We don't need to worry about overflow here as ::GetModuleFileName won't |
+ // return anything bigger than the max file system path (much fewer than max |
+ // of DWORD). |
+ DWORD dataSize = static_cast<DWORD>((::wcslen(exePath) + 1) * sizeof(WCHAR)); |
+ |
+ // In this sample, the app UI is registered to launch when the COM callback is |
+ // needed. |
+ // Other options might be to launch a background process instead that then |
+ // decides to launch |
+ // the UI if needed by that particular notification. |
+ return HRESULT_FROM_WIN32(::RegSetKeyValue( |
+ HKEY_CURRENT_USER, |
+ LR"(SOFTWARE\Classes\CLSID\{E65AECC7-DD9B-4D14-A4ED-73A5BEF1187A}" |
+ LR"\LocalServer32)", |
+ nullptr, REG_SZ, reinterpret_cast<const BYTE*>(exePath), dataSize)); |
+} |
+ |
+HRESULT RegisterAppForNotificationSupport() { |
+ CoTaskMemString appData; |
+ |
+ auto hr = ::SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, |
+ appData.GetAddressOf()); |
+ if (SUCCEEDED(hr)) { |
+ wchar_t shortcutPath[MAX_PATH]; |
+ hr = ::PathCchCombine( |
+ shortcutPath, ARRAYSIZE(shortcutPath), appData.Get(), |
+ LR"(Microsoft\Windows\Start Menu\Programs\Tester\tester.lnk)"); |
+ if (SUCCEEDED(hr)) { |
+ // MessageBoxW(NULL, shortcutPath, L"Title", MB_OK); |
+ DWORD attributes = ::GetFileAttributes(shortcutPath); |
+ bool fileExists = attributes < 0xFFFFFFF; |
+ if (!fileExists) { |
+ wchar_t exePath[MAX_PATH]; |
+ DWORD charWritten = |
+ ::GetModuleFileName(nullptr, exePath, ARRAYSIZE(exePath)); |
+ MessageBoxW(NULL, exePath, L"Title", MB_OK); |
+ hr = charWritten > 0 ? S_OK : HRESULT_FROM_WIN32(::GetLastError()); |
+ if (SUCCEEDED(hr)) { |
+ const wchar_t* arguments = L"--enable-features=NativeNotifications " |
+ L"--user-data-dir=R:\\p"; |
+ hr = InstallShortcut(shortcutPath, exePath, arguments); |
+ if (SUCCEEDED(hr)) { |
+ hr = RegisterComServer(exePath); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ if (!SUCCEEDED(hr)) { |
+ MessageBoxA(NULL, "RegisterAppForNotificationSupport() failed.", "Title", |
+ MB_OK); |
+ } |
+ return hr; |
+} |
+ |
+// Register activator for notifications |
+HRESULT RegisterActivator() { |
+ // Module<OutOfProc> needs a callback registered before it can be used. |
+ // Since we don't care about when it shuts down, we'll pass an empty lambda |
+ // here. |
+ Module<OutOfProc>::Create([] {}); |
+ |
+ // If a local server process only hosts the COM object then COM expects |
+ // the COM server host to shutdown when the references drop to zero. |
+ // Since the user might still be using the program after activating the |
+ // notification, |
+ // we don't want to shutdown immediately. Incrementing the object count tells |
+ // COM that |
+ // we aren't done yet. |
+ Module<OutOfProc>::GetModule().IncrementObjectCount(); |
+ |
+ return Module<OutOfProc>::GetModule().RegisterObjects(); |
+} |
+ |
+// Unregister our activator COM object |
+void UnregisterActivator() { |
+ Module<OutOfProc>::GetModule().UnregisterObjects(); |
+ |
+ Module<OutOfProc>::GetModule().DecrementObjectCount(); |
+} |
+ |
+class HackyRegister { |
+ public: |
+ HackyRegister() { |
+ } |
+ HRESULT Run() { |
+ HRESULT hr = RegisterActivator(); |
+ if (SUCCEEDED(hr)) |
+ has_reg = true; |
+ return hr; |
+ } |
+ ~HackyRegister() { |
+ if (has_reg) |
+ UnregisterActivator(); |
+ } |
+ private: |
+ bool has_reg = false; |
+}; |
+ |
+std::unique_ptr<HackyRegister> my_reg; |
+ |
+} // namespace |
+ |
+void PrepareChromeForWindows10() { |
+ RoInitializeWrapper winRtInitializer(RO_INIT_MULTITHREADED); |
+ |
+ // Experimental code: Attempt to respond to notificaiton events, using idea |
+ // from https://github.com/WindowsNotifications/desktop-toasts |
+ HRESULT hr = winRtInitializer; |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ hr = RegisterAppForNotificationSupport(); |
+ if (SUCCEEDED(hr)) { |
+ my_reg.reset(new HackyRegister()); |
+ hr = my_reg->Run(); |
+ if (SUCCEEDED(hr)) { |
+ // ::MessageBoxA(NULL, "Registered", "Title", MB_OK); |
+ } |
+ } |
+} |