// 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 "chrome/installer/util/taskbar_util.h"
#include <objbase.h>
#include <shellapi.h>
#include <shlobj.h>
#include <wrl/client.h>
#include "base/files/file_path.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/win/registry.h"
#include "base/win/windows_version.h"
#include "chrome/install_static/install_util.h"
namespace {
enum class PinnedListModifyCaller { kExplorer = 4 };
constexpr GUID CLSID_TaskbandPin = {
0x90aa3a4e,
0x1cba,
0x4233,
{0xb8, 0xbb, 0x53, 0x57, 0x73, 0xd4, 0x84, 0x49}};
constexpr wchar_t kInstallerPinned[] = L"InstallerPinned";
// Set by tests to control whether or not pinning to taskbar is allowed.
CanPinToTaskBarDelegateFunctionPtr g_can_pin_to_taskbar;
// Undocumented COM interface for manipulating taskbar pinned list.
class __declspec(uuid("0DD79AE2-D156-45D4-9EEB-3B549769E940")) IPinnedList3
: public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE EnumObjects() = 0;
virtual HRESULT STDMETHODCALLTYPE GetPinnableInfo() = 0;
virtual HRESULT STDMETHODCALLTYPE IsPinnable() = 0;
virtual HRESULT STDMETHODCALLTYPE Resolve() = 0;
virtual HRESULT STDMETHODCALLTYPE LegacyModify() = 0;
virtual HRESULT STDMETHODCALLTYPE GetChangeCount() = 0;
virtual HRESULT STDMETHODCALLTYPE IsPinned(PCIDLIST_ABSOLUTE) = 0;
virtual HRESULT STDMETHODCALLTYPE GetPinnedItem() = 0;
virtual HRESULT STDMETHODCALLTYPE GetAppIDForPinnedItem() = 0;
virtual HRESULT STDMETHODCALLTYPE ItemChangeNotify() = 0;
virtual HRESULT STDMETHODCALLTYPE UpdateForRemovedItemsAsNecessary() = 0;
virtual HRESULT STDMETHODCALLTYPE PinShellLink() = 0;
virtual HRESULT STDMETHODCALLTYPE GetPinnedItemForAppID() = 0;
virtual HRESULT STDMETHODCALLTYPE Modify(PCIDLIST_ABSOLUTE unpin,
PCIDLIST_ABSOLUTE pin,
PinnedListModifyCaller caller) = 0;
};
// ScopedPIDLFromPath class, and the idea of using IPinnedList3::Modify,
// are thanks to Gee Law <https://geelaw.blog/entries/msedge-pins/>
class ScopedPIDLFromPath {
public:
explicit ScopedPIDLFromPath(PCWSTR path)
: p_id_list_(ILCreateFromPath(path)) {}
~ScopedPIDLFromPath() {
if (p_id_list_)
ILFree(p_id_list_);
}
PIDLIST_ABSOLUTE Get() const { return p_id_list_; }
private:
PIDLIST_ABSOLUTE const p_id_list_;
};
// Returns the taskbar pinned list if successful, an empty ComPtr otherwise.
Microsoft::WRL::ComPtr<IPinnedList3> GetTaskbarPinnedList() {
if (base::win::GetVersion() < base::win::Version::WIN10_RS5)
return nullptr;
Microsoft::WRL::ComPtr<IPinnedList3> pinned_list;
if (FAILED(CoCreateInstance(CLSID_TaskbandPin, nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pinned_list)))) {
return nullptr;
}
return pinned_list;
}
// Use IPinnedList3 to pin shortcut to taskbar on WIN10_RS5 and above.
// Returns true if pinning was successful.
// static
bool PinShortcutWithIPinnedList3(const base::FilePath& shortcut) {
Microsoft::WRL::ComPtr<IPinnedList3> pinned_list = GetTaskbarPinnedList();
if (!pinned_list)
return false;
ScopedPIDLFromPath item_id_list(shortcut.value().data());
HRESULT hr = pinned_list->Modify(nullptr, item_id_list.Get(),
PinnedListModifyCaller::kExplorer);
return SUCCEEDED(hr);
}
// Use IPinnedList3 to unpin shortcut to taskbar on WIN10_RS5 and above.
// Returns true if unpinning was successful.
// static
bool UnpinShortcutWithIPinnedList3(const base::FilePath& shortcut) {
Microsoft::WRL::ComPtr<IPinnedList3> pinned_list = GetTaskbarPinnedList();
if (!pinned_list)
return false;
ScopedPIDLFromPath item_id_list(shortcut.value().data());
HRESULT hr = pinned_list->Modify(item_id_list.Get(), nullptr,
PinnedListModifyCaller::kExplorer);
return SUCCEEDED(hr);
}
} // namespace
bool CanPinShortcutToTaskbar() {
// "Pin to taskbar" isn't directly supported in Windows 10, but WIN10_RS5 has
// some undocumented interfaces to do pinning.
return base::win::GetVersion() >= base::win::Version::WIN10_RS5;
}
bool PinShortcutToTaskbar(const base::FilePath& shortcut) {
if (g_can_pin_to_taskbar && !(*g_can_pin_to_taskbar)()) {
return false;
}
return PinShortcutWithIPinnedList3(shortcut);
}
bool UnpinShortcutFromTaskbar(const base::FilePath& shortcut) {
return UnpinShortcutWithIPinnedList3(shortcut);
}
std::optional<bool> IsShortcutPinnedToTaskbar(const base::FilePath& shortcut) {
Microsoft::WRL::ComPtr<IPinnedList3> pinned_list = GetTaskbarPinnedList();
if (!pinned_list.Get())
return std::nullopt;
ScopedPIDLFromPath item_id_list(shortcut.value().data());
HRESULT hr = pinned_list->IsPinned(item_id_list.Get());
// S_OK means `shortcut` is pinned, S_FALSE means it's not pinned.
return SUCCEEDED(hr) ? std::optional<bool>(hr == S_OK) : std::nullopt;
}
void SetCanPinToTaskbarDelegate(CanPinToTaskBarDelegateFunctionPtr delegate) {
g_can_pin_to_taskbar = delegate;
}
bool SetInstallerPinnedChromeToTaskbar(bool installed) {
base::win::RegKey key;
if (key.Create(HKEY_CURRENT_USER, install_static::GetRegistryPath().c_str(),
KEY_SET_VALUE) == ERROR_SUCCESS) {
return key.WriteValue(kInstallerPinned, installed ? 1 : 0) == ERROR_SUCCESS;
}
return false;
}
std::optional<bool> GetInstallerPinnedChromeToTaskbar() {
base::win::RegKey key;
if (key.Open(HKEY_CURRENT_USER, install_static::GetRegistryPath().c_str(),
KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
DWORD installer_pinned = 0;
LONG result = key.ReadValueDW(kInstallerPinned, &installer_pinned);
if (result == ERROR_SUCCESS)
return installer_pinned != 0;
}
return std::nullopt;
}