summaryrefslogtreecommitdiffstats
path: root/browser/components/shell/Windows11TaskbarPinning.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/shell/Windows11TaskbarPinning.cpp')
-rw-r--r--browser/components/shell/Windows11TaskbarPinning.cpp344
1 files changed, 344 insertions, 0 deletions
diff --git a/browser/components/shell/Windows11TaskbarPinning.cpp b/browser/components/shell/Windows11TaskbarPinning.cpp
new file mode 100644
index 0000000000..350a58d59a
--- /dev/null
+++ b/browser/components/shell/Windows11TaskbarPinning.cpp
@@ -0,0 +1,344 @@
+/* -*- 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 "Windows11TaskbarPinning.h"
+#include "Windows11LimitedAccessFeatures.h"
+
+#include "nsWindowsHelpers.h"
+#include "MainThreadUtils.h"
+#include "nsThreadUtils.h"
+#include <strsafe.h>
+
+#include "mozilla/Result.h"
+#include "mozilla/ResultVariant.h"
+
+#include "mozilla/Logging.h"
+
+static mozilla::LazyLogModule sLog("Windows11TaskbarPinning");
+
+#define TASKBAR_PINNING_LOG(level, msg, ...) \
+ MOZ_LOG(sLog, level, (msg, ##__VA_ARGS__))
+
+#ifndef __MINGW32__ // WinRT headers not yet supported by MinGW
+
+# include <wrl.h>
+
+# include <inspectable.h>
+# include <roapi.h>
+# include <windows.services.store.h>
+# include <windows.foundation.h>
+# include <windows.ui.shell.h>
+
+using namespace mozilla;
+
+/**
+ * The Win32 SetEvent and WaitForSingleObject functions take HANDLE parameters
+ * which are typedefs of void*. When using nsAutoHandle, that means if you
+ * forget to call .get() first, everything still compiles and then doesn't work
+ * at runtime. For instance, calling SetEvent(mEvent) below would compile but
+ * not work at runtime and the waits would block forever.
+ * To ensure this isn't an issue, we wrap the event in a custom class here
+ * with the simple methods that we want on an event.
+ */
+class EventWrapper {
+ public:
+ EventWrapper() : mEvent(CreateEventW(nullptr, true, false, nullptr)) {}
+
+ void Set() { SetEvent(mEvent.get()); }
+
+ void Reset() { ResetEvent(mEvent.get()); }
+
+ void Wait() { WaitForSingleObject(mEvent.get(), INFINITE); }
+
+ private:
+ nsAutoHandle mEvent;
+};
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows;
+using namespace ABI::Windows::UI::Shell;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::ApplicationModel;
+
+static Result<ComPtr<ITaskbarManager>, HRESULT> InitializeTaskbar() {
+ ComPtr<IInspectable> taskbarStaticsInspectable;
+
+ TASKBAR_PINNING_LOG(LogLevel::Debug, "Initializing taskbar");
+
+ HRESULT hr = RoGetActivationFactory(
+ HStringReference(RuntimeClass_Windows_UI_Shell_TaskbarManager).Get(),
+ IID_ITaskbarManagerStatics, &taskbarStaticsInspectable);
+ if (FAILED(hr)) {
+ TASKBAR_PINNING_LOG(LogLevel::Debug,
+ "Taskbar: Failed to activate. HRESULT = 0x%lx", hr);
+ return Err(hr);
+ }
+
+ ComPtr<ITaskbarManagerStatics> taskbarStatics;
+
+ hr = taskbarStaticsInspectable.As(&taskbarStatics);
+ if (FAILED(hr)) {
+ TASKBAR_PINNING_LOG(LogLevel::Debug, "Failed statistics. HRESULT = 0x%lx",
+ hr);
+ return Err(hr);
+ }
+
+ ComPtr<ITaskbarManager> taskbarManager;
+
+ hr = taskbarStatics->GetDefault(&taskbarManager);
+ if (FAILED(hr)) {
+ TASKBAR_PINNING_LOG(LogLevel::Debug,
+ "Error getting TaskbarManager. HRESULT = 0x%lx", hr);
+ return Err(hr);
+ }
+
+ TASKBAR_PINNING_LOG(LogLevel::Debug,
+ "TaskbarManager retrieved successfully!");
+ return taskbarManager;
+}
+
+Win11PinToTaskBarResult PinCurrentAppToTaskbarWin11(
+ bool aCheckOnly, const nsAString& aAppUserModelId,
+ nsAutoString aShortcutPath) {
+ MOZ_DIAGNOSTIC_ASSERT(!NS_IsMainThread(),
+ "PinCurrentAppToTaskbarWin11 should be called off main "
+ "thread only. It blocks, waiting on things to execute "
+ "asynchronously on the main thread.");
+
+ {
+ RefPtr<Win11LimitedAccessFeaturesInterface> limitedAccessFeatures =
+ CreateWin11LimitedAccessFeaturesInterface();
+ auto result =
+ limitedAccessFeatures->Unlock(Win11LimitedAccessFeatureType::Taskbar);
+ if (result.isErr()) {
+ auto hr = result.unwrapErr();
+ TASKBAR_PINNING_LOG(LogLevel::Debug,
+ "Taskbar unlock: Error. HRESULT = 0x%lx", hr);
+ return {hr, Win11PinToTaskBarResultStatus::NotSupported};
+ }
+
+ if (result.unwrap() == false) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar unlock: failed. Not supported on this version of Windows.");
+ return {S_OK, Win11PinToTaskBarResultStatus::NotSupported};
+ }
+ }
+
+ HRESULT hr;
+ Win11PinToTaskBarResultStatus resultStatus =
+ Win11PinToTaskBarResultStatus::NotSupported;
+
+ EventWrapper event;
+
+ // Everything related to the taskbar and pinning must be done on the main /
+ // user interface thread or Windows will cause them to fail.
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "PinCurrentAppToTaskbarWin11", [&event, &hr, &resultStatus, aCheckOnly] {
+ auto CompletedOperations =
+ [&event, &resultStatus](Win11PinToTaskBarResultStatus status) {
+ resultStatus = status;
+ event.Set();
+ };
+
+ auto result = InitializeTaskbar();
+ if (result.isErr()) {
+ hr = result.unwrapErr();
+ return CompletedOperations(
+ Win11PinToTaskBarResultStatus::NotSupported);
+ }
+
+ ComPtr<ITaskbarManager> taskbar = result.unwrap();
+ boolean supported;
+ hr = taskbar->get_IsSupported(&supported);
+ if (FAILED(hr) || !supported) {
+ if (FAILED(hr)) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: error checking if supported. HRESULT = 0x%lx", hr);
+ } else {
+ TASKBAR_PINNING_LOG(LogLevel::Debug, "Taskbar: not supported.");
+ }
+ return CompletedOperations(
+ Win11PinToTaskBarResultStatus::NotSupported);
+ }
+
+ if (aCheckOnly) {
+ TASKBAR_PINNING_LOG(LogLevel::Debug, "Taskbar: check succeeded.");
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Success);
+ }
+
+ boolean isAllowed = false;
+ hr = taskbar->get_IsPinningAllowed(&isAllowed);
+ if (FAILED(hr) || !isAllowed) {
+ if (FAILED(hr)) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: error checking if pinning is allowed. HRESULT = "
+ "0x%lx",
+ hr);
+ } else {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: is pinning allowed error or isn't allowed right now. "
+ "It's not clear when it will be allowed. Possibly after a "
+ "reboot.");
+ }
+ return CompletedOperations(
+ Win11PinToTaskBarResultStatus::NotCurrentlyAllowed);
+ }
+
+ ComPtr<IAsyncOperation<bool>> isPinnedOperation = nullptr;
+ hr = taskbar->IsCurrentAppPinnedAsync(&isPinnedOperation);
+ if (FAILED(hr)) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: is current app pinned operation failed. HRESULT = "
+ "0x%lx",
+ hr);
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
+ }
+
+ // Copy the taskbar; don't use it as a reference.
+ // With the async calls, it's not guaranteed to still be valid
+ // if sent as a reference.
+ // resultStatus and event are not defined on the main thread and will
+ // be alive until the async functions complete, so they can be used as
+ // references.
+ auto isPinnedCallback = Callback<IAsyncOperationCompletedHandler<
+ bool>>([taskbar, &event, &resultStatus, &hr](
+ IAsyncOperation<bool>* asyncInfo,
+ AsyncStatus status) mutable -> HRESULT {
+ auto CompletedOperations =
+ [&event,
+ &resultStatus](Win11PinToTaskBarResultStatus status) -> HRESULT {
+ resultStatus = status;
+ event.Set();
+ return S_OK;
+ };
+
+ bool asyncOpSucceeded = status == AsyncStatus::Completed;
+ if (!asyncOpSucceeded) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: is pinned operation failed to complete.");
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
+ }
+
+ unsigned char isCurrentAppPinned = false;
+ hr = asyncInfo->GetResults(&isCurrentAppPinned);
+ if (FAILED(hr)) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: is current app pinned check failed. HRESULT = 0x%lx",
+ hr);
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
+ }
+
+ if (isCurrentAppPinned) {
+ TASKBAR_PINNING_LOG(LogLevel::Debug,
+ "Taskbar: current app is already pinned.");
+ return CompletedOperations(
+ Win11PinToTaskBarResultStatus::AlreadyPinned);
+ }
+
+ ComPtr<IAsyncOperation<bool>> requestPinOperation = nullptr;
+ hr = taskbar->RequestPinCurrentAppAsync(&requestPinOperation);
+ if (FAILED(hr)) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: request pin current app operation creation failed. "
+ "HRESULT = 0x%lx",
+ hr);
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
+ }
+
+ auto pinAppCallback = Callback<IAsyncOperationCompletedHandler<
+ bool>>([CompletedOperations, &hr](
+ IAsyncOperation<bool>* asyncInfo,
+ AsyncStatus status) -> HRESULT {
+ bool asyncOpSucceeded = status == AsyncStatus::Completed;
+ if (!asyncOpSucceeded) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: request pin current app operation did not "
+ "complete.");
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
+ }
+
+ unsigned char successfullyPinned = 0;
+ hr = asyncInfo->GetResults(&successfullyPinned);
+ if (FAILED(hr) || !successfullyPinned) {
+ if (FAILED(hr)) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: request pin current app operation failed to pin "
+ "due to error. HRESULT = 0x%lx",
+ hr);
+ } else {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: request pin current app operation failed to pin");
+ }
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
+ }
+
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: request pin current app operation succeeded");
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Success);
+ });
+
+ HRESULT pinOperationHR =
+ requestPinOperation->put_Completed(pinAppCallback.Get());
+ if (FAILED(pinOperationHR)) {
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: request pin operation failed when setting completion "
+ "callback. HRESULT = 0x%lx",
+ hr);
+ hr = pinOperationHR;
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
+ }
+
+ // DO NOT SET event HERE. It will be set in the pin operation
+ // callback As in, operations are not completed, so don't call
+ // CompletedOperations
+ return S_OK;
+ });
+
+ HRESULT isPinnedOperationHR =
+ isPinnedOperation->put_Completed(isPinnedCallback.Get());
+ if (FAILED(isPinnedOperationHR)) {
+ hr = isPinnedOperationHR;
+ TASKBAR_PINNING_LOG(
+ LogLevel::Debug,
+ "Taskbar: is pinned operation failed when setting completion "
+ "callback. HRESULT = 0x%lx",
+ hr);
+ return CompletedOperations(Win11PinToTaskBarResultStatus::Failed);
+ }
+
+ // DO NOT SET event HERE. It will be set in the is pin operation
+ // callback As in, operations are not completed, so don't call
+ // CompletedOperations
+ }));
+
+ // block until the pinning is completed on the main thread
+ event.Wait();
+
+ return {hr, resultStatus};
+}
+
+#else // MINGW32 implementation below
+
+Win11PinToTaskBarResult PinCurrentAppToTaskbarWin11(
+ bool aCheckOnly, const nsAString& aAppUserModelId,
+ nsAutoString aShortcutPath) {
+ return {S_OK, Win11PinToTaskBarResultStatus::NotSupported};
+}
+
+#endif // #ifndef __MINGW32__ // WinRT headers not yet supported by MinGW