diff --git a/.github/actions/spell-check/dictionary/microsoft.txt b/.github/actions/spell-check/dictionary/microsoft.txt index 0e94847f887..2f520a5c2f3 100644 --- a/.github/actions/spell-check/dictionary/microsoft.txt +++ b/.github/actions/spell-check/dictionary/microsoft.txt @@ -18,6 +18,7 @@ pgo pgosweep powerrename powershell +propkey pscustomobject robocopy SACLs diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 2a02e80dd13..2c217e50b7c 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -693,6 +693,8 @@ namespace winrt::TerminalApp::implementation // Register for directory change notification. _RegisterSettingsChange(); + + Jumplist::UpdateJumplist(*_settings); } // Method Description: @@ -851,6 +853,8 @@ namespace winrt::TerminalApp::implementation _RefreshThemeRoutine(); _ApplyStartupTaskStateChange(); + + Jumplist::UpdateJumplist(*_settings); } // Method Description: diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index a5e811a886b..18204401fdc 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -8,6 +8,7 @@ #include "Tab.h" #include "CascadiaSettings.h" #include "TerminalPage.h" +#include "Jumplist.h" #include "../../cascadia/inc/cppwinrt_utils.h" namespace winrt::TerminalApp::implementation diff --git a/src/cascadia/TerminalApp/Jumplist.cpp b/src/cascadia/TerminalApp/Jumplist.cpp new file mode 100644 index 00000000000..625367c7410 --- /dev/null +++ b/src/cascadia/TerminalApp/Jumplist.cpp @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "Jumplist.h" + +#include +#include + +using namespace winrt::TerminalApp; + +// This property key isn't already defined in propkey.h, but is used by UWP Jumplist to determine the icon of the jumplist item. +// IShellLink's SetIconLocation isn't going to read "ms-appx://" icon paths, so we'll need to use this to set the icon. +DEFINE_PROPERTYKEY(PKEY_AppUserModel_DestListLogoUri, 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3, 29); +#define INIT_PKEY_AppUserModel_DestListLogoUri \ + { \ + { 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 29 \ + } + +// Method Description: +// - Updates the items of the Jumplist based on the given settings. +// Arguments: +// - settings - The settings object to update the jumplist with. +// Return Value: +// - +HRESULT Jumplist::UpdateJumplist(const TerminalApp::CascadiaSettings& settings) noexcept +{ + try + { + auto jumplistInstance = winrt::create_instance(CLSID_DestinationList, CLSCTX_ALL); + + // Start the Jumplist edit transaction + uint32_t slots; + winrt::com_ptr jumplistItems; + jumplistItems.capture(jumplistInstance, &ICustomDestinationList::BeginList, &slots); + + // It's easier to clear the list and re-add everything. The settings aren't + // updated often, and there likely isn't a huge amount of items to add. + RETURN_IF_FAILED(jumplistItems->Clear()); + + // Update the list of profiles. + RETURN_IF_FAILED(_updateProfiles(jumplistItems.get(), settings.GetProfiles())); + + // TODO GH#1571: Add items from the future customizable new tab dropdown as well. + // This could either replace the default profiles, or be added alongside them. + + // Add the items to the jumplist Task section. + // The Tasks section is immutable by the user, unlike the destinations + // section that can have its items pinned and removed. + RETURN_IF_FAILED(jumplistInstance->AddUserTasks(jumplistItems.get())); + + RETURN_IF_FAILED(jumplistInstance->CommitList()); + + return S_OK; + } + CATCH_RETURN(); +} + +// Method Description: +// - Creates and adds a ShellLink object to the Jumplist for each profile. +// Arguments: +// - jumplistItems - The jumplist item list +// - profiles - The profiles to add to the jumplist +// Return Value: +// - S_OK or HRESULT failure code. +[[nodiscard]] HRESULT Jumplist::_updateProfiles(IObjectCollection* jumplistItems, const gsl::span& profiles) noexcept +{ + try + { + for (const auto& profile : profiles) + { + // Craft the arguments following "wt.exe" + auto args = fmt::format(L"-p {}", to_hstring(profile.Guid())); + + // Create the shell link object for the profile + winrt::com_ptr shLink; + RETURN_IF_FAILED(_createShellLink(profile.Name(), profile.GetExpandedIconPath(), args, shLink.put())); + + RETURN_IF_FAILED(jumplistItems->AddObject(shLink.get())); + } + + return S_OK; + } + CATCH_RETURN(); +} + +// Method Description: +// - Creates a ShellLink object. Each item in a jumplist is a ShellLink, which is sort of +// like a shortcut. It requires the path to the application (wt.exe), the arguments to pass, +// and the path to the icon for the jumplist item. The path to the application isn't passed +// into this function, as we'll determine it with GetModuleFileName. +// Arguments: +// - name: The name of the item displayed in the jumplist. +// - path: The path to the icon for the jumplist item. +// - args: The arguments to pass along with wt.exe +// - shLink: The shell link object to return. +// Return Value: +// - S_OK or HRESULT failure code. +[[nodiscard]] HRESULT Jumplist::_createShellLink(const std::wstring_view name, + const std::wstring_view path, + const std::wstring_view args, + IShellLinkW** shLink) noexcept +{ + try + { + auto sh = winrt::create_instance(CLSID_ShellLink, CLSCTX_ALL); + + std::filesystem::path module{ wil::GetModuleFileNameW(nullptr) }; + RETURN_IF_FAILED(sh->SetPath(module.c_str())); + RETURN_IF_FAILED(sh->SetArguments(args.data())); + + PROPVARIANT titleProp; + titleProp.vt = VT_LPWSTR; + titleProp.pwszVal = const_cast(name.data()); + + PROPVARIANT iconProp; + iconProp.vt = VT_LPWSTR; + iconProp.pwszVal = const_cast(path.data()); + + auto propStore{ sh.as() }; + RETURN_IF_FAILED(propStore->SetValue(PKEY_Title, titleProp)); + RETURN_IF_FAILED(propStore->SetValue(PKEY_AppUserModel_DestListLogoUri, iconProp)); + + RETURN_IF_FAILED(propStore->Commit()); + + *shLink = sh.detach(); + + return S_OK; + } + CATCH_RETURN(); +} diff --git a/src/cascadia/TerminalApp/Jumplist.h b/src/cascadia/TerminalApp/Jumplist.h new file mode 100644 index 00000000000..ba3649c0311 --- /dev/null +++ b/src/cascadia/TerminalApp/Jumplist.h @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// Module Name: +// - Jumplist.h +// +// Abstract: +// - The Jumplist is the menu that pops up when right clicking a pinned +// item in the taskbar. This class handles updating the Terminal's jumplist +// using the Terminal's settings. +// + +#pragma once + +#include "CascadiaSettings.h" +#include "Profile.h" + +struct IObjectCollection; +struct IShellLinkW; + +class Jumplist +{ +public: + static HRESULT UpdateJumplist(const TerminalApp::CascadiaSettings& settings) noexcept; + +private: + [[nodiscard]] static HRESULT _updateProfiles(IObjectCollection* jumplistItems, const gsl::span& profiles) noexcept; + [[nodiscard]] static HRESULT _createShellLink(const std::wstring_view name, const std::wstring_view path, const std::wstring_view args, IShellLinkW** shLink) noexcept; +}; diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index f4a0ee3c25e..85d3a858790 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -72,6 +72,7 @@ + MinMaxCloseControl.xaml @@ -155,6 +156,7 @@ + MinMaxCloseControl.xaml