diff options
Diffstat (limited to 'widget/windows/WinTaskbar.cpp')
-rw-r--r-- | widget/windows/WinTaskbar.cpp | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/widget/windows/WinTaskbar.cpp b/widget/windows/WinTaskbar.cpp new file mode 100644 index 0000000000..56608503da --- /dev/null +++ b/widget/windows/WinTaskbar.cpp @@ -0,0 +1,493 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +#include "nsIWinTaskbar.h" +#include "WinTaskbar.h" +#include "TaskbarPreview.h" +#include "nsITaskbarPreviewController.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/widget/JumpListBuilder.h" +#include <nsError.h> +#include <nsCOMPtr.h> +#include <nsIWidget.h> +#include <nsIBaseWindow.h> +#include <nsServiceManagerUtils.h> +#include "nsIXULAppInfo.h" +#include "nsILegacyJumpListBuilder.h" +#include "nsUXThemeData.h" +#include "nsWindow.h" +#include "WinUtils.h" +#include "TaskbarTabPreview.h" +#include "TaskbarWindowPreview.h" +#include "LegacyJumpListBuilder.h" +#include "nsWidgetsCID.h" +#include "nsPIDOMWindow.h" +#include "nsAppDirectoryServiceDefs.h" +#include "mozilla/Preferences.h" +#include "nsAppRunner.h" +#include "nsXREDirProvider.h" +#include "mozilla/widget/WinRegistry.h" +#include <io.h> +#include <propvarutil.h> +#include <propkey.h> +#include <shellapi.h> + +static NS_DEFINE_CID(kLegacyJumpListBuilderCID, + NS_WIN_LEGACYJUMPLISTBUILDER_CID); + +namespace { + +HWND GetHWNDFromDocShell(nsIDocShell* aShell) { + nsCOMPtr<nsIBaseWindow> baseWindow( + do_QueryInterface(reinterpret_cast<nsISupports*>(aShell))); + + if (!baseWindow) return nullptr; + + nsCOMPtr<nsIWidget> widget; + baseWindow->GetMainWidget(getter_AddRefs(widget)); + + return widget ? (HWND)widget->GetNativeData(NS_NATIVE_WINDOW) : nullptr; +} + +HWND GetHWNDFromDOMWindow(mozIDOMWindow* dw) { + nsCOMPtr<nsIWidget> widget; + + if (!dw) return nullptr; + + nsCOMPtr<nsPIDOMWindowInner> window = nsPIDOMWindowInner::From(dw); + return GetHWNDFromDocShell(window->GetDocShell()); +} + +nsresult SetWindowAppUserModelProp(mozIDOMWindow* aParent, + const nsString& aIdentifier) { + NS_ENSURE_ARG_POINTER(aParent); + + if (aIdentifier.IsEmpty()) return NS_ERROR_INVALID_ARG; + + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDOMWindow(aParent), GA_ROOT); + + if (!toplevelHWND) return NS_ERROR_INVALID_ARG; + + RefPtr<IPropertyStore> pPropStore; + if (FAILED(SHGetPropertyStoreForWindow(toplevelHWND, IID_IPropertyStore, + getter_AddRefs(pPropStore)))) { + return NS_ERROR_INVALID_ARG; + } + + PROPVARIANT pv; + if (FAILED(InitPropVariantFromString(aIdentifier.get(), &pv))) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + if (FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv)) || + FAILED(pPropStore->Commit())) { + rv = NS_ERROR_FAILURE; + } + + PropVariantClear(&pv); + + return rv; +} + +/////////////////////////////////////////////////////////////////////////////// +// default nsITaskbarPreviewController + +class DefaultController final : public nsITaskbarPreviewController { + ~DefaultController() {} + HWND mWnd; + + public: + explicit DefaultController(HWND hWnd) : mWnd(hWnd) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARPREVIEWCONTROLLER +}; + +NS_IMETHODIMP +DefaultController::GetWidth(uint32_t* aWidth) { + RECT r; + ::GetClientRect(mWnd, &r); + *aWidth = r.right; + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::GetHeight(uint32_t* aHeight) { + RECT r; + ::GetClientRect(mWnd, &r); + *aHeight = r.bottom; + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::GetThumbnailAspectRatio(float* aThumbnailAspectRatio) { + uint32_t width, height; + GetWidth(&width); + GetHeight(&height); + if (!height) height = 1; + + *aThumbnailAspectRatio = width / float(height); + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::RequestThumbnail(nsITaskbarPreviewCallback* aCallback, + uint32_t width, uint32_t height) { + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::RequestPreview(nsITaskbarPreviewCallback* aCallback) { + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::OnClose(void) { + MOZ_ASSERT_UNREACHABLE( + "OnClose should not be called for " + "TaskbarWindowPreviews"); + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::OnActivate(bool* rAcceptActivation) { + *rAcceptActivation = true; + MOZ_ASSERT_UNREACHABLE( + "OnActivate should not be called for " + "TaskbarWindowPreviews"); + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::OnClick(nsITaskbarPreviewButton* button) { return NS_OK; } + +NS_IMPL_ISUPPORTS(DefaultController, nsITaskbarPreviewController) +} // namespace + +namespace mozilla { +namespace widget { + +/////////////////////////////////////////////////////////////////////////////// +// nsIWinTaskbar + +NS_IMPL_ISUPPORTS(WinTaskbar, nsIWinTaskbar) + +bool WinTaskbar::Initialize() { + if (mTaskbar) return true; + + ::CoInitialize(nullptr); + HRESULT hr = + ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, + IID_ITaskbarList4, (void**)&mTaskbar); + if (FAILED(hr)) return false; + + hr = mTaskbar->HrInit(); + if (FAILED(hr)) { + // This may fail with shell extensions like blackbox installed. + NS_WARNING("Unable to initialize taskbar"); + NS_RELEASE(mTaskbar); + return false; + } + return true; +} + +WinTaskbar::WinTaskbar() : mTaskbar(nullptr) {} + +WinTaskbar::~WinTaskbar() { + if (mTaskbar) { // match successful Initialize() call + NS_RELEASE(mTaskbar); + ::CoUninitialize(); + } +} + +// static +bool WinTaskbar::GenerateAppUserModelID(nsAString& aAppUserModelId, + bool aPrivateBrowsing) { + // If marked as such in prefs, use a hash of the profile path for the id + // instead of the install path hash setup by the installer. + if (Preferences::GetBool("taskbar.grouping.useprofile", false)) { + nsCOMPtr<nsIFile> profileDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + bool exists = false; + if (profileDir && NS_SUCCEEDED(profileDir->Exists(&exists)) && exists) { + nsAutoCString path; + if (NS_SUCCEEDED(profileDir->GetPersistentDescriptor(path))) { + nsAutoString id; + id.AppendInt(HashString(path)); + if (!id.IsEmpty()) { + aAppUserModelId.Assign(id); + return true; + } + } + } + } + + // The default value is set by the installer and is stored in the registry + // under (HKLM||HKCU)/Software/Mozilla/Firefox/TaskBarIDs. If for any reason + // hash generation operation fails, the installer will not store a value in + // the registry or set ids on shortcuts. A lack of an id can also occur for + // zipped builds. + nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1"); + nsCString appName; + if (appInfo && NS_SUCCEEDED(appInfo->GetName(appName))) { + nsAutoString regKey; + regKey.AssignLiteral("Software\\Mozilla\\"); + AppendASCIItoUTF16(appName, regKey); + regKey.AppendLiteral("\\TaskBarIDs"); + + WCHAR path[MAX_PATH]; + if (GetModuleFileNameW(nullptr, path, MAX_PATH)) { + wchar_t* slash = wcsrchr(path, '\\'); + if (!slash) return false; + *slash = '\0'; // no trailing slash + + nsDependentString pathStr(path); + for (auto* rootKey : {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}) { + if (auto aumid = WinRegistry::GetString(rootKey, regKey, pathStr)) { + aAppUserModelId = std::move(*aumid); + break; + } + } + } + } + + // If we haven't found an ID yet then use the install hash. In xpcshell tests + // the directory provider may not have been initialized so bypass in this + // case. + if (aAppUserModelId.IsEmpty() && gDirServiceProvider) { + gDirServiceProvider->GetInstallHash(aAppUserModelId); + } + + if (aPrivateBrowsing) { + aAppUserModelId.AppendLiteral(";PrivateBrowsingAUMID"); + } + + return !aAppUserModelId.IsEmpty(); +} + +// static +bool WinTaskbar::GetAppUserModelID(nsAString& aAppUserModelId, + bool aPrivateBrowsing) { + // If an ID has already been set then use that. + PWSTR id; + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(&id))) { + aAppUserModelId.Assign(id); + CoTaskMemFree(id); + } + + return GenerateAppUserModelID(aAppUserModelId, aPrivateBrowsing); +} + +NS_IMETHODIMP +WinTaskbar::GetDefaultGroupId(nsAString& aDefaultGroupId) { + if (!GetAppUserModelID(aDefaultGroupId)) return NS_ERROR_UNEXPECTED; + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::GetDefaultPrivateGroupId(nsAString& aDefaultPrivateGroupId) { + if (!GetAppUserModelID(aDefaultPrivateGroupId, true)) + return NS_ERROR_UNEXPECTED; + + return NS_OK; +} + +// (static) Called from AppShell +bool WinTaskbar::RegisterAppUserModelID() { + nsAutoString uid; + if (!GetAppUserModelID(uid)) return false; + + return SUCCEEDED(SetCurrentProcessExplicitAppUserModelID(uid.get())); +} + +NS_IMETHODIMP +WinTaskbar::GetAvailable(bool* aAvailable) { + // ITaskbarList4::HrInit() may fail with shell extensions like blackbox + // installed. Initialize early to return available=false in those cases. + *aAvailable = Initialize(); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::CreateTaskbarTabPreview(nsIDocShell* shell, + nsITaskbarPreviewController* controller, + nsITaskbarTabPreview** _retval) { + if (!Initialize()) return NS_ERROR_NOT_AVAILABLE; + + NS_ENSURE_ARG_POINTER(shell); + NS_ENSURE_ARG_POINTER(controller); + + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT); + + if (!toplevelHWND) return NS_ERROR_INVALID_ARG; + + RefPtr<TaskbarTabPreview> preview( + new TaskbarTabPreview(mTaskbar, controller, toplevelHWND, shell)); + if (!preview) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = preview->Init(); + if (NS_FAILED(rv)) { + return rv; + } + + preview.forget(_retval); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::GetTaskbarWindowPreview(nsIDocShell* shell, + nsITaskbarWindowPreview** _retval) { + if (!Initialize()) return NS_ERROR_NOT_AVAILABLE; + + NS_ENSURE_ARG_POINTER(shell); + + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT); + + if (!toplevelHWND) return NS_ERROR_INVALID_ARG; + + nsWindow* window = WinUtils::GetNSWindowPtr(toplevelHWND); + + if (!window) return NS_ERROR_FAILURE; + + nsCOMPtr<nsITaskbarWindowPreview> preview = window->GetTaskbarPreview(); + if (!preview) { + RefPtr<DefaultController> defaultController = + new DefaultController(toplevelHWND); + + TaskbarWindowPreview* previewRaw = new TaskbarWindowPreview( + mTaskbar, defaultController, toplevelHWND, shell); + if (!previewRaw) { + return NS_ERROR_OUT_OF_MEMORY; + } + + preview = previewRaw; + + nsresult rv = previewRaw->Init(); + if (NS_FAILED(rv)) { + return rv; + } + window->SetTaskbarPreview(preview); + } + + preview.forget(_retval); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::GetTaskbarProgress(nsIDocShell* shell, + nsITaskbarProgress** _retval) { + nsCOMPtr<nsITaskbarWindowPreview> preview; + nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(preview, _retval); +} + +NS_IMETHODIMP +WinTaskbar::GetOverlayIconController( + nsIDocShell* shell, nsITaskbarOverlayIconController** _retval) { + nsCOMPtr<nsITaskbarWindowPreview> preview; + nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(preview, _retval); +} + +NS_IMETHODIMP +WinTaskbar::CreateLegacyJumpListBuilder( + bool aPrivateBrowsing, nsILegacyJumpListBuilder** aJumpListBuilder) { + nsresult rv; + + if (LegacyJumpListBuilder::sBuildingList) return NS_ERROR_ALREADY_INITIALIZED; + + nsCOMPtr<nsILegacyJumpListBuilder> builder = + do_CreateInstance(kLegacyJumpListBuilderCID, &rv); + if (NS_FAILED(rv)) return NS_ERROR_UNEXPECTED; + + NS_IF_ADDREF(*aJumpListBuilder = builder); + + nsAutoString aumid; + GenerateAppUserModelID(aumid, aPrivateBrowsing); + builder->SetAppUserModelID(aumid); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::CreateJumpListBuilder(bool aPrivateBrowsing, + nsIJumpListBuilder** aJumpListBuilder) { + nsAutoString aumid; + GenerateAppUserModelID(aumid, aPrivateBrowsing); + + nsCOMPtr<nsIJumpListBuilder> builder = new JumpListBuilder(aumid); + if (!builder) { + return NS_ERROR_UNEXPECTED; + } + + NS_IF_ADDREF(*aJumpListBuilder = builder); + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::GetGroupIdForWindow(mozIDOMWindow* aParent, + nsAString& aIdentifier) { + NS_ENSURE_ARG_POINTER(aParent); + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDOMWindow(aParent), GA_ROOT); + if (!toplevelHWND) return NS_ERROR_INVALID_ARG; + RefPtr<IPropertyStore> pPropStore; + if (FAILED(SHGetPropertyStoreForWindow(toplevelHWND, IID_IPropertyStore, + getter_AddRefs(pPropStore)))) { + return NS_ERROR_INVALID_ARG; + } + PROPVARIANT pv; + PropVariantInit(&pv); + auto cleanupPropVariant = MakeScopeExit([&] { PropVariantClear(&pv); }); + if (FAILED(pPropStore->GetValue(PKEY_AppUserModel_ID, &pv))) { + return NS_ERROR_FAILURE; + } + if (pv.vt != VT_LPWSTR) { + // This can happen when there is no window specific group ID set + // It's not an error case so we have to check for empty strings + // returned from the function. + return NS_OK; + } + aIdentifier.Assign(char16ptr_t(pv.pwszVal)); + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::SetGroupIdForWindow(mozIDOMWindow* aParent, + const nsAString& aIdentifier) { + return SetWindowAppUserModelProp(aParent, nsString(aIdentifier)); +} + +NS_IMETHODIMP +WinTaskbar::PrepareFullScreen(void* aHWND, bool aFullScreen) { + if (!Initialize()) return NS_ERROR_NOT_AVAILABLE; + + NS_ENSURE_ARG_POINTER(aHWND); + + if (!::IsWindow((HWND)aHWND)) return NS_ERROR_INVALID_ARG; + + HRESULT hr = mTaskbar->MarkFullscreenWindow((HWND)aHWND, aFullScreen); + if (FAILED(hr)) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +} // namespace widget +} // namespace mozilla |