From 65b22b9abba26fa94150505cee4444dcb4e42182 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 28 Apr 2021 17:25:48 -0500 Subject: [PATCH] Add `desktop` param to `globalSummon`; set _quake = toCurrent (#9954) This adds support for the `desktop` param to the `globalSummon` action. It accepts 3 values: * `toCurrent` (default): The window moves to the current desktop when it's summoned * `any`: We don't care what desktop the window is on. We'll go to the desktop the window is on when we summon it. * `onCurrent`: We'll only try to summon the MRU window on this desktop when summoning a window. * When combined with `name`, if there's a window matching `name`, we'll move it to this desktop. * If there's not a window on this desktop, and `name` is omitted, then we'll make a new window. `quakeMode` was also updated to use `toCurrent` behavior by default. ## References * Original thread: #653 * Spec: #9274 * megathread: #8888 ## PR Checklist * [x] Checks some boxes in #8888 * [x] closes https://github.com/microsoft/terminal/projects/5#card-59030845 * [x] I work here * [x] Tests added * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments S/O to https://github.com/microsoft/PowerToys, who graciously let us use `VirtualDesktopUtils` for figuring out what desktop is the current desktop. Yea, that's all we needed that entire file for. No, there isn't an API for this (_surprised-pikachu.png_) ## Validation Steps Performed Played with this for a while, and it's amazing. --- NOTICE.md | 30 + .../Microsoft.Terminal.RemotingLib.vcxproj | 6 + src/cascadia/Remoting/Monarch.cpp | 7 +- src/cascadia/Remoting/Monarch.idl | 4 +- src/cascadia/Remoting/Peasant.cpp | 8 +- src/cascadia/Remoting/Peasant.h | 4 +- src/cascadia/Remoting/Peasant.idl | 11 +- .../Remoting/SummonWindowBehavior.cpp | 5 + src/cascadia/Remoting/SummonWindowBehavior.h | 35 ++ .../Remoting/SummonWindowSelectionArgs.h | 3 + .../TerminalSettingsModel/ActionArgs.h | 9 +- .../TerminalSettingsModel/ActionArgs.idl | 8 + .../TerminalSettingsSerializationHelpers.h | 9 + .../UnitTests_Remoting/RemotingTests.cpp | 583 +++++++++++++++++- src/cascadia/WindowsTerminal/AppHost.cpp | 68 +- src/cascadia/WindowsTerminal/AppHost.h | 6 +- src/cascadia/WindowsTerminal/IslandWindow.cpp | 4 +- .../WindowsTerminal/VirtualDesktopUtils.cpp | 173 ++++++ .../WindowsTerminal/VirtualDesktopUtils.h | 11 + .../WindowsTerminal/WindowsTerminal.vcxproj | 2 + 20 files changed, 960 insertions(+), 26 deletions(-) create mode 100644 src/cascadia/Remoting/SummonWindowBehavior.cpp create mode 100644 src/cascadia/Remoting/SummonWindowBehavior.h create mode 100644 src/cascadia/WindowsTerminal/VirtualDesktopUtils.cpp create mode 100644 src/cascadia/WindowsTerminal/VirtualDesktopUtils.h diff --git a/NOTICE.md b/NOTICE.md index a63cd5664..ccff11e17 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -251,3 +251,33 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` + +## `VirtualDesktopUtils` + +**Source**: [https://github.com/microsoft/PowerToys](https://github.com/microsoft/PowerToys) + +### License + +``` +The MIT License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` diff --git a/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj b/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj index 34f70956a..2241d3c0e 100644 --- a/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj +++ b/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj @@ -28,6 +28,9 @@ Monarch.idl + + Peasant.idl + Peasant.idl @@ -60,6 +63,9 @@ Monarch.idl + + Peasant.idl + Peasant.idl diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index 66cbe6ad2..fc1be97e3 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -711,7 +711,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // If no name was provided, then just summon the MRU window. if (searchedForName.empty()) { - windowId = _getMostRecentPeasantID(true); + // Use the value of the `desktop` arg to determine if we should + // limit to the current desktop (desktop:onCurrent) or not + // (desktop:any or desktop:toCurrent) + windowId = _getMostRecentPeasantID(args.OnCurrentDesktop()); } else { @@ -720,7 +723,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation } if (auto targetPeasant{ _getPeasant(windowId) }) { - targetPeasant.Summon(); + targetPeasant.Summon(args.SummonBehavior()); args.FoundMatch(true); } } diff --git a/src/cascadia/Remoting/Monarch.idl b/src/cascadia/Remoting/Monarch.idl index ca62cdc04..d87d780b3 100644 --- a/src/cascadia/Remoting/Monarch.idl +++ b/src/cascadia/Remoting/Monarch.idl @@ -22,13 +22,15 @@ namespace Microsoft.Terminal.Remoting SummonWindowSelectionArgs(); SummonWindowSelectionArgs(String windowName); String WindowName; + Boolean OnCurrentDesktop; // TODO GH#8888 Other options: - // * CurrentDesktop // * CurrentMonitor Boolean FoundMatch; + SummonWindowBehavior SummonBehavior; } + [default_interface] runtimeclass Monarch { Monarch(); diff --git a/src/cascadia/Remoting/Peasant.cpp b/src/cascadia/Remoting/Peasant.cpp index 990d1d591..d7b58b73b 100644 --- a/src/cascadia/Remoting/Peasant.cpp +++ b/src/cascadia/Remoting/Peasant.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "Peasant.h" #include "CommandlineArgs.h" +#include "SummonWindowBehavior.h" #include "Peasant.g.cpp" #include "../../types/inc/utils.hpp" @@ -126,14 +127,17 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // - // Return Value: // - - void Peasant::Summon() + void Peasant::Summon(const Remoting::SummonWindowBehavior& summonBehavior) { - _SummonRequestedHandlers(*this, nullptr); + auto localCopy = winrt::make(summonBehavior); TraceLoggingWrite(g_hRemotingProvider, "Peasant_Summon", TraceLoggingUInt64(GetID(), "peasantID", "Our ID"), + TraceLoggingUInt64(localCopy->MoveToCurrentDesktop(), "MoveToCurrentDesktop", "true if we should move to the current desktop"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + + _SummonRequestedHandlers(*this, localCopy); } // Method Description: diff --git a/src/cascadia/Remoting/Peasant.h b/src/cascadia/Remoting/Peasant.h index 72c672c2e..7b5b44e81 100644 --- a/src/cascadia/Remoting/Peasant.h +++ b/src/cascadia/Remoting/Peasant.h @@ -24,7 +24,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation bool ExecuteCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args); void ActivateWindow(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args); - void Summon(); + void Summon(const Remoting::SummonWindowBehavior& summonBehavior); void RequestIdentifyWindows(); void DisplayWindowId(); void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args); @@ -39,7 +39,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation TYPED_EVENT(IdentifyWindowsRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs); - TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); + TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior); private: Peasant(const uint64_t testPID); diff --git a/src/cascadia/Remoting/Peasant.idl b/src/cascadia/Remoting/Peasant.idl index 6ff404b73..af74cf3d0 100644 --- a/src/cascadia/Remoting/Peasant.idl +++ b/src/cascadia/Remoting/Peasant.idl @@ -30,6 +30,13 @@ namespace Microsoft.Terminal.Remoting Windows.Foundation.DateTime ActivatedTime { get; }; }; + [default_interface] runtimeclass SummonWindowBehavior { + SummonWindowBehavior(); + Boolean MoveToCurrentDesktop; + // Other options: + // * CurrentMonitor + } + interface IPeasant { CommandlineArgs InitialArgs { get; }; @@ -46,14 +53,14 @@ namespace Microsoft.Terminal.Remoting String WindowName { get; }; void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested - void Summon(); + void Summon(SummonWindowBehavior behavior); event Windows.Foundation.TypedEventHandler WindowActivated; event Windows.Foundation.TypedEventHandler ExecuteCommandlineRequested; event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; event Windows.Foundation.TypedEventHandler DisplayWindowIdRequested; event Windows.Foundation.TypedEventHandler RenameRequested; - event Windows.Foundation.TypedEventHandler SummonRequested; + event Windows.Foundation.TypedEventHandler SummonRequested; }; [default_interface] runtimeclass Peasant : IPeasant diff --git a/src/cascadia/Remoting/SummonWindowBehavior.cpp b/src/cascadia/Remoting/SummonWindowBehavior.cpp new file mode 100644 index 000000000..516acff06 --- /dev/null +++ b/src/cascadia/Remoting/SummonWindowBehavior.cpp @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +#include "pch.h" +#include "SummonWindowBehavior.h" +#include "SummonWindowBehavior.g.cpp" diff --git a/src/cascadia/Remoting/SummonWindowBehavior.h b/src/cascadia/Remoting/SummonWindowBehavior.h new file mode 100644 index 000000000..c460ef988 --- /dev/null +++ b/src/cascadia/Remoting/SummonWindowBehavior.h @@ -0,0 +1,35 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Class Name: +- SummonWindowBehavior.h + +Abstract: +- TODO! + +--*/ + +#pragma once + +#include "SummonWindowBehavior.g.h" +#include "../cascadia/inc/cppwinrt_utils.h" + +namespace winrt::Microsoft::Terminal::Remoting::implementation +{ + struct SummonWindowBehavior : public SummonWindowBehaviorT + { + public: + SummonWindowBehavior() = default; + WINRT_PROPERTY(bool, MoveToCurrentDesktop, true); + + public: + SummonWindowBehavior(const Remoting::SummonWindowBehavior& other) : + _MoveToCurrentDesktop{ other.MoveToCurrentDesktop() } {}; + }; +} + +namespace winrt::Microsoft::Terminal::Remoting::factory_implementation +{ + BASIC_FACTORY(SummonWindowBehavior); +} diff --git a/src/cascadia/Remoting/SummonWindowSelectionArgs.h b/src/cascadia/Remoting/SummonWindowSelectionArgs.h index 4aec6488f..2696925ef 100644 --- a/src/cascadia/Remoting/SummonWindowSelectionArgs.h +++ b/src/cascadia/Remoting/SummonWindowSelectionArgs.h @@ -30,7 +30,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation _WindowName{ name } {}; WINRT_PROPERTY(winrt::hstring, WindowName); + WINRT_PROPERTY(bool, FoundMatch, false); + WINRT_PROPERTY(bool, OnCurrentDesktop, false); + WINRT_PROPERTY(SummonWindowBehavior, SummonBehavior); }; } diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 681326916..3074db1b2 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -1044,17 +1044,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { GlobalSummonArgs() = default; WINRT_PROPERTY(winrt::hstring, Name, L""); + WINRT_PROPERTY(Model::DesktopBehavior, Desktop, Model::DesktopBehavior::ToCurrent); static constexpr std::string_view NameKey{ "name" }; + static constexpr std::string_view DesktopKey{ "desktop" }; public: hstring GenerateName() const; bool Equals(const IActionArgs& other) { - if (auto otherAsUs = other.try_as(); otherAsUs) + if (auto otherAsUs = other.try_as()) { - return otherAsUs->_Name == _Name; + return otherAsUs->_Name == _Name && + otherAsUs->_Desktop == _Desktop; } return false; }; @@ -1063,12 +1066,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // LOAD BEARING: Not using make_self here _will_ break you in the future! auto args = winrt::make_self(); JsonUtils::GetValueForKey(json, NameKey, args->_Name); + JsonUtils::GetValueForKey(json, DesktopKey, args->_Desktop); return { *args, {} }; } IActionArgs Copy() const { auto copy{ winrt::make_self() }; copy->_Name = _Name; + copy->_Desktop = _Desktop; return *copy; } // SPECIAL! This deserializer creates a GlobalSummonArgs with the diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index ca29300fe..abf52e50b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -84,6 +84,13 @@ namespace Microsoft.Terminal.Settings.Model Disabled, }; + enum DesktopBehavior + { + Any, + ToCurrent, + OnCurrent, + }; + [default_interface] runtimeclass NewTerminalArgs { NewTerminalArgs(); NewTerminalArgs(Int32 profileIndex); @@ -256,5 +263,6 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass GlobalSummonArgs : IActionArgs { String Name { get; }; + DesktopBehavior Desktop { get; }; }; } diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index bdda3eef5..57607607e 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -448,3 +448,12 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::WindowingMode) pair_type{ "useExisting", ValueType::UseExisting }, }; }; + +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::DesktopBehavior) +{ + JSON_MAPPINGS(3) = { + pair_type{ "any", ValueType::Any }, + pair_type{ "toCurrent", ValueType::ToCurrent }, + pair_type{ "onCurrent", ValueType::OnCurrent }, + }; +}; diff --git a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp index 694dcef8f..5fa03dbef 100644 --- a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp +++ b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp @@ -36,6 +36,31 @@ using namespace winrt::Microsoft::Terminal; namespace RemotingUnitTests { + struct MockDesktopManager : implements + { + HRESULT GetWindowDesktopId(HWND /*topLevelWindow*/, GUID* /*desktopId*/) + { + VERIFY_IS_TRUE(false, L"We shouldn't need GetWindowDesktopId in the tests."); + return E_FAIL; + } + HRESULT MoveWindowToDesktop(HWND /*topLevelWindow*/, REFGUID /*desktopId*/) + { + VERIFY_IS_TRUE(false, L"We shouldn't need GetWindowDesktopId in the tests."); + return E_FAIL; + } + HRESULT IsWindowOnCurrentVirtualDesktop(HWND topLevelWindow, BOOL* onCurrentDesktop) + { + if (pfnIsWindowOnCurrentVirtualDesktop) + { + return pfnIsWindowOnCurrentVirtualDesktop(topLevelWindow, onCurrentDesktop); + } + VERIFY_IS_TRUE(false, L"You didn't set up the pfnIsWindowOnCurrentVirtualDesktop for this test!"); + return E_FAIL; + } + + std::function pfnIsWindowOnCurrentVirtualDesktop; + }; + // This is a silly helper struct. // It will always throw an hresult_error on any of its methods. // @@ -60,13 +85,13 @@ namespace RemotingUnitTests Remoting::CommandlineArgs InitialArgs() { throw winrt::hresult_error{}; } Remoting::WindowActivatedArgs GetLastActivatedArgs() { throw winrt::hresult_error{}; } void RequestRename(const Remoting::RenameRequestArgs& /*args*/) { throw winrt::hresult_error{}; } - void Summon() { throw winrt::hresult_error{}; }; + void Summon(const Remoting::SummonWindowBehavior& /*args*/) { throw winrt::hresult_error{}; }; TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, Remoting::WindowActivatedArgs); TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, Remoting::CommandlineArgs); TYPED_EVENT(IdentifyWindowsRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, Remoting::RenameRequestArgs); - TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); + TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, Remoting::SummonWindowBehavior); }; class RemotingTests @@ -114,6 +139,10 @@ namespace RemotingUnitTests TEST_METHOD(TestSummonNamedDeadWindow); TEST_METHOD(TestSummonMostRecentDeadWindow); + TEST_METHOD(TestSummonOnCurrent); + TEST_METHOD(TestSummonOnCurrentWithName); + TEST_METHOD(TestSummonOnCurrentDeadWindow); + TEST_CLASS_SETUP(ClassSetup) { return true; @@ -1945,4 +1974,554 @@ namespace RemotingUnitTests VERIFY_IS_TRUE(args.FoundMatch()); } + void RemotingTests::TestSummonOnCurrent() + { + Log::Comment(L"Tests summoning a window, using OnCurrentDesktop to only" + L"select windows on the current desktop."); + + const winrt::guid guid1{ Utils::GuidFromString(L"{11111111-1111-1111-1111-111111111111}") }; + const winrt::guid guid2{ Utils::GuidFromString(L"{22222222-2222-2222-2222-222222222222}") }; + + constexpr auto monarch0PID = 12345u; + constexpr auto peasant1PID = 23456u; + constexpr auto peasant2PID = 34567u; + constexpr auto peasant3PID = 45678u; + + com_ptr m0; + m0.attach(new Remoting::implementation::Monarch(monarch0PID)); + + com_ptr p1; + p1.attach(new Remoting::implementation::Peasant(peasant1PID)); + + com_ptr p2; + p2.attach(new Remoting::implementation::Peasant(peasant2PID)); + + com_ptr p3; + p3.attach(new Remoting::implementation::Peasant(peasant3PID)); + + VERIFY_IS_NOT_NULL(m0); + VERIFY_IS_NOT_NULL(p1); + VERIFY_IS_NOT_NULL(p2); + VERIFY_IS_NOT_NULL(p3); + p1->WindowName(L"one"); + p2->WindowName(L"two"); + p3->WindowName(L"three"); + + VERIFY_ARE_EQUAL(0, p1->GetID()); + VERIFY_ARE_EQUAL(0, p2->GetID()); + VERIFY_ARE_EQUAL(0, p3->GetID()); + + m0->AddPeasant(*p1); + m0->AddPeasant(*p2); + m0->AddPeasant(*p3); + + VERIFY_ARE_EQUAL(1, p1->GetID()); + VERIFY_ARE_EQUAL(2, p2->GetID()); + VERIFY_ARE_EQUAL(3, p3->GetID()); + + VERIFY_ARE_EQUAL(3u, m0->_peasants.size()); + + bool p1ExpectedToBeSummoned = false; + bool p2ExpectedToBeSummoned = false; + bool p3ExpectedToBeSummoned = false; + + p1->SummonRequested([&](auto&&, auto&&) { + Log::Comment(L"p1 summoned"); + VERIFY_IS_TRUE(p1ExpectedToBeSummoned); + }); + p2->SummonRequested([&](auto&&, auto&&) { + Log::Comment(L"p2 summoned"); + VERIFY_IS_TRUE(p2ExpectedToBeSummoned); + }); + p3->SummonRequested([&](auto&&, auto&&) { + Log::Comment(L"p3 summoned"); + VERIFY_IS_TRUE(p3ExpectedToBeSummoned); + }); + + { + Log::Comment(L"Activate the first peasant, first desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(), + p1->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid1, + winrt::clock().now() }; + p1->ActivateWindow(activatedArgs); + } + { + Log::Comment(L"Activate the second peasant, second desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p2->GetID(), + p2->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid2, + winrt::clock().now() }; + p2->ActivateWindow(activatedArgs); + } + { + Log::Comment(L"Activate the third peasant, first desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p3->GetID(), + p3->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid1, + winrt::clock().now() }; + p3->ActivateWindow(activatedArgs); + } + + Log::Comment(L"Create a mock IVirtualDesktopManager to handle checking if a window is on a given desktop"); + winrt::com_ptr manager; + manager.attach(new MockDesktopManager()); + m0->_desktopManager = manager.try_as(); + + auto firstCallback = [&](HWND h, BOOL* result) -> HRESULT { + Log::Comment(L"firstCallback: Checking if window is on desktop 1"); + + const uint64_t hwnd = reinterpret_cast(h); + if (hwnd == peasant1PID || hwnd == peasant3PID) + { + *result = true; + } + else if (hwnd == peasant2PID) + { + *result = false; + } + else + { + VERIFY_IS_TRUE(false, L"IsWindowOnCurrentVirtualDesktop called with unexpected value"); + } + return S_OK; + }; + manager->pfnIsWindowOnCurrentVirtualDesktop = firstCallback; + + Remoting::SummonWindowSelectionArgs args; + + Log::Comment(L"Summon window three - it is the MRU on desktop 1"); + p3ExpectedToBeSummoned = true; + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + + { + Log::Comment(L"Activate the first peasant, first desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(), + p1->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid1, + winrt::clock().now() }; + p1->ActivateWindow(activatedArgs); + } + + Log::Comment(L"Summon window one - it is the MRU on desktop 1"); + p1ExpectedToBeSummoned = true; + p2ExpectedToBeSummoned = false; + p3ExpectedToBeSummoned = false; + args.FoundMatch(false); + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + + Log::Comment(L"Now we'll pretend we switched to desktop 2"); + + auto secondCallback = [&](HWND h, BOOL* result) -> HRESULT { + Log::Comment(L"secondCallback: Checking if window is on desktop 2"); + const uint64_t hwnd = reinterpret_cast(h); + if (hwnd == peasant1PID || hwnd == peasant3PID) + { + *result = false; + } + else if (hwnd == peasant2PID) + { + *result = true; + } + else + { + VERIFY_IS_TRUE(false, L"IsWindowOnCurrentVirtualDesktop called with unexpected value"); + } + return S_OK; + }; + manager->pfnIsWindowOnCurrentVirtualDesktop = secondCallback; + + Log::Comment(L"Summon window one - it is the MRU on desktop 2"); + p1ExpectedToBeSummoned = false; + p2ExpectedToBeSummoned = true; + p3ExpectedToBeSummoned = false; + args.FoundMatch(false); + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + + { + Log::Comment(L"Activate the third peasant, second desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p3->GetID(), + p3->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid2, + winrt::clock().now() }; + p3->ActivateWindow(activatedArgs); + } + + auto thirdCallback = [&](HWND h, BOOL* result) -> HRESULT { + Log::Comment(L"thirdCallback: Checking if window is on desktop 2. (windows 2 and 3 are)"); + const uint64_t hwnd = reinterpret_cast(h); + if (hwnd == peasant1PID) + { + *result = false; + } + else if (hwnd == peasant2PID || hwnd == peasant3PID) + { + *result = true; + } + else + { + VERIFY_IS_TRUE(false, L"IsWindowOnCurrentVirtualDesktop called with unexpected value"); + } + return S_OK; + }; + manager->pfnIsWindowOnCurrentVirtualDesktop = thirdCallback; + + Log::Comment(L"Summon window three - it is the MRU on desktop 2"); + p1ExpectedToBeSummoned = false; + p2ExpectedToBeSummoned = false; + p3ExpectedToBeSummoned = true; + args.FoundMatch(false); + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + + Log::Comment(L"Now we'll pretend we switched to desktop 1"); + + auto fourthCallback = [&](HWND h, BOOL* result) -> HRESULT { + Log::Comment(L"fourthCallback: Checking if window is on desktop 1. (window 1 is)"); + const uint64_t hwnd = reinterpret_cast(h); + if (hwnd == peasant1PID) + { + *result = true; + } + else if (hwnd == peasant2PID || hwnd == peasant3PID) + { + *result = false; + } + else + { + VERIFY_IS_TRUE(false, L"IsWindowOnCurrentVirtualDesktop called with unexpected value"); + } + return S_OK; + }; + manager->pfnIsWindowOnCurrentVirtualDesktop = fourthCallback; + + Log::Comment(L"Summon window one - it is the only window on desktop 1"); + p1ExpectedToBeSummoned = true; + p2ExpectedToBeSummoned = false; + p3ExpectedToBeSummoned = false; + args.FoundMatch(false); + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + + Log::Comment(L"Now we'll pretend we switched to desktop 3"); + + auto fifthCallback = [&](HWND h, BOOL* result) -> HRESULT { + Log::Comment(L"fifthCallback: Checking if window is on desktop 3. (none are)"); + const uint64_t hwnd = reinterpret_cast(h); + if (hwnd == peasant1PID || hwnd == peasant2PID || hwnd == peasant3PID) + { + *result = false; + } + else + { + VERIFY_IS_TRUE(false, L"IsWindowOnCurrentVirtualDesktop called with unexpected value"); + } + return S_OK; + }; + manager->pfnIsWindowOnCurrentVirtualDesktop = fifthCallback; + + Log::Comment(L"This summon won't find a window."); + p1ExpectedToBeSummoned = false; + p2ExpectedToBeSummoned = false; + p3ExpectedToBeSummoned = false; + args.FoundMatch(false); + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_FALSE(args.FoundMatch()); + } + + void RemotingTests::TestSummonOnCurrentWithName() + { + Log::Comment(L"Test that specifying a WindowName forces us to ignore OnCurrentDesktop"); + + const winrt::guid guid1{ Utils::GuidFromString(L"{11111111-1111-1111-1111-111111111111}") }; + const winrt::guid guid2{ Utils::GuidFromString(L"{22222222-2222-2222-2222-222222222222}") }; + + constexpr auto monarch0PID = 12345u; + constexpr auto peasant1PID = 23456u; + constexpr auto peasant2PID = 34567u; + constexpr auto peasant3PID = 45678u; + + com_ptr m0; + m0.attach(new Remoting::implementation::Monarch(monarch0PID)); + + com_ptr p1; + p1.attach(new Remoting::implementation::Peasant(peasant1PID)); + + com_ptr p2; + p2.attach(new Remoting::implementation::Peasant(peasant2PID)); + + com_ptr p3; + p3.attach(new Remoting::implementation::Peasant(peasant3PID)); + + VERIFY_IS_NOT_NULL(m0); + VERIFY_IS_NOT_NULL(p1); + VERIFY_IS_NOT_NULL(p2); + VERIFY_IS_NOT_NULL(p3); + p1->WindowName(L"one"); + p2->WindowName(L"two"); + p3->WindowName(L"three"); + + VERIFY_ARE_EQUAL(0, p1->GetID()); + VERIFY_ARE_EQUAL(0, p2->GetID()); + VERIFY_ARE_EQUAL(0, p3->GetID()); + + m0->AddPeasant(*p1); + m0->AddPeasant(*p2); + m0->AddPeasant(*p3); + + VERIFY_ARE_EQUAL(1, p1->GetID()); + VERIFY_ARE_EQUAL(2, p2->GetID()); + VERIFY_ARE_EQUAL(3, p3->GetID()); + + VERIFY_ARE_EQUAL(3u, m0->_peasants.size()); + + bool p1ExpectedToBeSummoned = false; + bool p2ExpectedToBeSummoned = false; + bool p3ExpectedToBeSummoned = false; + + p1->SummonRequested([&](auto&&, auto&&) { + Log::Comment(L"p1 summoned"); + VERIFY_IS_TRUE(p1ExpectedToBeSummoned); + }); + p2->SummonRequested([&](auto&&, auto&&) { + Log::Comment(L"p2 summoned"); + VERIFY_IS_TRUE(p2ExpectedToBeSummoned); + }); + p3->SummonRequested([&](auto&&, auto&&) { + Log::Comment(L"p3 summoned"); + VERIFY_IS_TRUE(p3ExpectedToBeSummoned); + }); + + { + Log::Comment(L"Activate the first peasant, first desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(), + p1->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid1, + winrt::clock().now() }; + p1->ActivateWindow(activatedArgs); + } + { + Log::Comment(L"Activate the second peasant, second desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p2->GetID(), + p2->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid2, + winrt::clock().now() }; + p2->ActivateWindow(activatedArgs); + } + { + Log::Comment(L"Activate the third peasant, first desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p3->GetID(), + p3->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid1, + winrt::clock().now() }; + p3->ActivateWindow(activatedArgs); + } + + Log::Comment(L"Create a mock IVirtualDesktopManager to handle checking if a window is on a given desktop"); + winrt::com_ptr manager; + manager.attach(new MockDesktopManager()); + m0->_desktopManager = manager.try_as(); + + auto firstCallback = [&](HWND h, BOOL* result) -> HRESULT { + Log::Comment(L"firstCallback: Checking if window is on desktop 1"); + + const uint64_t hwnd = reinterpret_cast(h); + if (hwnd == peasant1PID || hwnd == peasant3PID) + { + *result = true; + } + else if (hwnd == peasant2PID) + { + *result = false; + } + else + { + VERIFY_IS_TRUE(false, L"IsWindowOnCurrentVirtualDesktop called with unexpected value"); + } + return S_OK; + }; + manager->pfnIsWindowOnCurrentVirtualDesktop = firstCallback; + + Remoting::SummonWindowSelectionArgs args; + + Log::Comment(L"Summon window three - it is the MRU on desktop 1"); + p3ExpectedToBeSummoned = true; + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + + Log::Comment(L"Look for window 1 by name. When given a name, we don't care about OnCurrentDesktop."); + p1ExpectedToBeSummoned = true; + p2ExpectedToBeSummoned = false; + p3ExpectedToBeSummoned = false; + args.FoundMatch(false); + args.WindowName(L"one"); + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + + Log::Comment(L"Look for window 2 by name. When given a name, we don't care about OnCurrentDesktop."); + p1ExpectedToBeSummoned = false; + p2ExpectedToBeSummoned = true; + p3ExpectedToBeSummoned = false; + args.FoundMatch(false); + args.WindowName(L"two"); + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + + Log::Comment(L"Look for window 3 by name. When given a name, we don't care about OnCurrentDesktop."); + p1ExpectedToBeSummoned = false; + p2ExpectedToBeSummoned = false; + p3ExpectedToBeSummoned = true; + args.FoundMatch(false); + args.WindowName(L"three"); + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + } + + void RemotingTests::TestSummonOnCurrentDeadWindow() + { + Log::Comment(L"Test that we can summon a window on the current desktop," + L" when the MRU window on that desktop dies."); + + const winrt::guid guid1{ Utils::GuidFromString(L"{11111111-1111-1111-1111-111111111111}") }; + const winrt::guid guid2{ Utils::GuidFromString(L"{22222222-2222-2222-2222-222222222222}") }; + + constexpr auto monarch0PID = 12345u; + constexpr auto peasant1PID = 23456u; + constexpr auto peasant2PID = 34567u; + constexpr auto peasant3PID = 45678u; + + com_ptr m0; + m0.attach(new Remoting::implementation::Monarch(monarch0PID)); + + com_ptr p1; + p1.attach(new Remoting::implementation::Peasant(peasant1PID)); + + com_ptr p2; + p2.attach(new Remoting::implementation::Peasant(peasant2PID)); + + com_ptr p3; + p3.attach(new Remoting::implementation::Peasant(peasant3PID)); + + VERIFY_IS_NOT_NULL(m0); + VERIFY_IS_NOT_NULL(p1); + VERIFY_IS_NOT_NULL(p2); + VERIFY_IS_NOT_NULL(p3); + p1->WindowName(L"one"); + p2->WindowName(L"two"); + p3->WindowName(L"three"); + + VERIFY_ARE_EQUAL(0, p1->GetID()); + VERIFY_ARE_EQUAL(0, p2->GetID()); + VERIFY_ARE_EQUAL(0, p3->GetID()); + + m0->AddPeasant(*p1); + m0->AddPeasant(*p2); + m0->AddPeasant(*p3); + + VERIFY_ARE_EQUAL(1, p1->GetID()); + VERIFY_ARE_EQUAL(2, p2->GetID()); + VERIFY_ARE_EQUAL(3, p3->GetID()); + + VERIFY_ARE_EQUAL(3u, m0->_peasants.size()); + + bool p1ExpectedToBeSummoned = false; + bool p2ExpectedToBeSummoned = false; + bool p3ExpectedToBeSummoned = false; + + p1->SummonRequested([&](auto&&, auto&&) { + Log::Comment(L"p1 summoned"); + VERIFY_IS_TRUE(p1ExpectedToBeSummoned); + }); + p2->SummonRequested([&](auto&&, auto&&) { + Log::Comment(L"p2 summoned"); + VERIFY_IS_TRUE(p2ExpectedToBeSummoned); + }); + p3->SummonRequested([&](auto&&, auto&&) { + Log::Comment(L"p3 summoned"); + VERIFY_IS_TRUE(p3ExpectedToBeSummoned); + }); + + { + Log::Comment(L"Activate the first peasant, first desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(), + p1->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid1, + winrt::clock().now() }; + p1->ActivateWindow(activatedArgs); + } + { + Log::Comment(L"Activate the second peasant, second desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p2->GetID(), + p2->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid2, + winrt::clock().now() }; + p2->ActivateWindow(activatedArgs); + } + { + Log::Comment(L"Activate the third peasant, first desktop"); + Remoting::WindowActivatedArgs activatedArgs{ p3->GetID(), + p3->GetPID(), // USE PID as HWND, because these values don't _really_ matter + guid1, + winrt::clock().now() }; + p3->ActivateWindow(activatedArgs); + } + + Log::Comment(L"Create a mock IVirtualDesktopManager to handle checking if a window is on a given desktop"); + winrt::com_ptr manager; + manager.attach(new MockDesktopManager()); + m0->_desktopManager = manager.try_as(); + + auto firstCallback = [&](HWND h, BOOL* result) -> HRESULT { + Log::Comment(L"firstCallback: Checking if window is on desktop 1"); + + const uint64_t hwnd = reinterpret_cast(h); + if (hwnd == peasant1PID || hwnd == peasant3PID) + { + *result = true; + } + else if (hwnd == peasant2PID) + { + *result = false; + } + else + { + VERIFY_IS_TRUE(false, L"IsWindowOnCurrentVirtualDesktop called with unexpected value"); + } + return S_OK; + }; + manager->pfnIsWindowOnCurrentVirtualDesktop = firstCallback; + + Remoting::SummonWindowSelectionArgs args; + + Log::Comment(L"Summon window three - it is the MRU on desktop 1"); + p3ExpectedToBeSummoned = true; + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + + Log::Comment(L"Kill window 3. Window 1 is now the MRU on desktop 1."); + RemotingTests::_killPeasant(m0, p3->GetID()); + + Log::Comment(L"Summon window three - it is the MRU on desktop 1"); + p1ExpectedToBeSummoned = true; + p2ExpectedToBeSummoned = false; + p3ExpectedToBeSummoned = false; + args.FoundMatch(false); + args.OnCurrentDesktop(true); + m0->SummonWindow(args); + VERIFY_IS_TRUE(args.FoundMatch()); + } } diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index f4054fbb7..3430e69ba 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -8,6 +8,7 @@ #include "../types/inc/User32Utils.hpp" #include "../WinRTUtils/inc/WtExeUtils.h" #include "resource.h" +#include "VirtualDesktopUtils.h" using namespace winrt::Windows::UI; using namespace winrt::Windows::UI::Composition; @@ -686,6 +687,12 @@ void AppHost::_GlobalHotkeyPressed(const long hotkeyIndex) { Remoting::SummonWindowSelectionArgs args{ summonArgs.Name() }; + // desktop:any - MoveToCurrentDesktop=false, OnCurrentDesktop=false + // desktop:toCurrent - MoveToCurrentDesktop=true, OnCurrentDesktop=false + // desktop:onCurrent - MoveToCurrentDesktop=false, OnCurrentDesktop=true + args.OnCurrentDesktop(summonArgs.Desktop() == Settings::Model::DesktopBehavior::OnCurrent); + args.SummonBehavior().MoveToCurrentDesktop(summonArgs.Desktop() == Settings::Model::DesktopBehavior::ToCurrent); + _windowManager.SummonWindow(args); if (args.FoundMatch()) { @@ -740,24 +747,65 @@ winrt::fire_and_forget AppHost::_createNewTerminalWindow(Settings::Model::Global co_return; } -void AppHost::_HandleSummon(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*args*/) +// Method Description: +// - Helper to initialize our instance of IVirtualDesktopManager. If we already +// got one, then this will just return true. Otherwise, we'll try and init a +// new instance of one, and store that. +// - This will return false if we weren't able to initialize one, which I'm not +// sure is actually possible. +// Arguments: +// - +// Return Value: +// - true iff _desktopManager points to a non-null instance of IVirtualDesktopManager +bool AppHost::_LazyLoadDesktopManager() { - _window->SummonWindow(); + if (_desktopManager == nullptr) + { + try + { + _desktopManager = winrt::create_instance(__uuidof(VirtualDesktopManager)); + } + CATCH_LOG(); + } + + return _desktopManager != nullptr; } +void AppHost::_HandleSummon(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const Remoting::SummonWindowBehavior& args) +{ + _window->SummonWindow(); + + if (args != nullptr && args.MoveToCurrentDesktop()) + { + if (_LazyLoadDesktopManager()) + { + GUID currentlyActiveDesktop{ 0 }; + if (VirtualDesktopUtils::GetCurrentVirtualDesktopId(¤tlyActiveDesktop)) + { + LOG_IF_FAILED(_desktopManager->MoveWindowToDesktop(_window->GetHandle(), currentlyActiveDesktop)); + } + // If GetCurrentVirtualDesktopId failed, then just leave the window + // where it is. Nothing else to be done :/ + } + } +} + +// Method Description: +// - This gets the GUID of the desktop our window is currently on. It does NOT +// get the GUID of the desktop that's currently active. +// Arguments: +// - +// Return Value: +// - the GUID of the desktop our window is currently on GUID AppHost::_CurrentDesktopGuid() { GUID currentDesktopGuid{ 0 }; - try + const auto manager = winrt::create_instance(__uuidof(VirtualDesktopManager)); + if (_LazyLoadDesktopManager()) { - const auto manager = winrt::create_instance(__uuidof(VirtualDesktopManager)); - if (manager) - { - LOG_IF_FAILED(manager->GetWindowDesktopId(_window->GetHandle(), ¤tDesktopGuid)); - } + LOG_IF_FAILED(_desktopManager->GetWindowDesktopId(_window->GetHandle(), ¤tDesktopGuid)); } - CATCH_LOG(); return currentDesktopGuid; } diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 9288ee560..6abfb511b 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -31,6 +31,8 @@ private: std::vector _hotkeys{}; winrt::Windows::Foundation::Collections::IMap _hotkeyActions{ nullptr }; + winrt::com_ptr _desktopManager{ nullptr }; + void _HandleCommandlineArgs(); void _HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode); @@ -59,7 +61,7 @@ private: const winrt::Windows::Foundation::IInspectable& args); void _GlobalHotkeyPressed(const long hotkeyIndex); void _HandleSummon(const winrt::Windows::Foundation::IInspectable& sender, - const winrt::Windows::Foundation::IInspectable& args); + const winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior& args); winrt::fire_and_forget _IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable sender, const winrt::Windows::Foundation::IInspectable args); @@ -70,6 +72,8 @@ private: GUID _CurrentDesktopGuid(); + bool _LazyLoadDesktopManager(); + winrt::fire_and_forget _setupGlobalHotkeys(); winrt::fire_and_forget _createNewTerminalWindow(winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs args); void _HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& sender, diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 7a6d6f4be..a4c249a5b 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -956,9 +956,9 @@ void IslandWindow::UnsetHotkeys(const std::vector(hotkeyList.size()); i++) { - LOG_IF_WIN32_BOOL_FALSE(UnregisterHotKey(_window.get(), static_cast(i))); + LOG_IF_WIN32_BOOL_FALSE(UnregisterHotKey(_window.get(), i)); } } diff --git a/src/cascadia/WindowsTerminal/VirtualDesktopUtils.cpp b/src/cascadia/WindowsTerminal/VirtualDesktopUtils.cpp new file mode 100644 index 000000000..a912e7e1b --- /dev/null +++ b/src/cascadia/WindowsTerminal/VirtualDesktopUtils.cpp @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// Shamelessly copied from microsoft/PowerToys, at +// https://github.com/microsoft/PowerToys/blob/master/src/modules/fancyzones/lib/VirtualDesktopUtils.cpp +// +// The code style is left (relatively) untouched, as to make contributions +// from/to the upstream source easier. `NewGetCurrentDesktopId` was added in +// April 2021. + +#include "pch.h" + +#include "VirtualDesktopUtils.h" + +// Non-Localizable strings +namespace NonLocalizable +{ + const wchar_t RegCurrentVirtualDesktop[] = L"CurrentVirtualDesktop"; + const wchar_t RegVirtualDesktopIds[] = L"VirtualDesktopIDs"; + const wchar_t RegKeyVirtualDesktops[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops"; + const wchar_t RegKeyVirtualDesktopsFromSession[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SessionInfo\\%d\\VirtualDesktops"; +} + +namespace VirtualDesktopUtils +{ + // Look for the guid stored as the value `CurrentVirtualDesktop` under the + // key + // `HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops` + bool NewGetCurrentDesktopId(GUID* desktopId) + { + wil::unique_hkey key{}; + if (RegOpenKeyExW(HKEY_CURRENT_USER, NonLocalizable::RegKeyVirtualDesktops, 0, KEY_ALL_ACCESS, &key) == ERROR_SUCCESS) + { + GUID value{}; + DWORD size = sizeof(GUID); + if (RegQueryValueExW(key.get(), NonLocalizable::RegCurrentVirtualDesktop, 0, nullptr, reinterpret_cast(&value), &size) == ERROR_SUCCESS) + { + *desktopId = value; + return true; + } + } + return false; + } + + bool GetDesktopIdFromCurrentSession(GUID* desktopId) + { + DWORD sessionId; + if (!ProcessIdToSessionId(GetCurrentProcessId(), &sessionId)) + { + return false; + } + + wchar_t sessionKeyPath[256]{}; + if (FAILED(StringCchPrintfW(sessionKeyPath, ARRAYSIZE(sessionKeyPath), NonLocalizable::RegKeyVirtualDesktopsFromSession, sessionId))) + { + return false; + } + + wil::unique_hkey key{}; + if (RegOpenKeyExW(HKEY_CURRENT_USER, sessionKeyPath, 0, KEY_ALL_ACCESS, &key) == ERROR_SUCCESS) + { + GUID value{}; + DWORD size = sizeof(GUID); + if (RegQueryValueExW(key.get(), NonLocalizable::RegCurrentVirtualDesktop, 0, nullptr, reinterpret_cast(&value), &size) == ERROR_SUCCESS) + { + *desktopId = value; + return true; + } + } + return false; + } + + bool GetVirtualDesktopIds(HKEY hKey, std::vector& ids) + { + if (!hKey) + { + return false; + } + DWORD bufferCapacity; + // request regkey binary buffer capacity only + if (RegQueryValueExW(hKey, NonLocalizable::RegVirtualDesktopIds, 0, nullptr, nullptr, &bufferCapacity) != ERROR_SUCCESS) + { + return false; + } + std::unique_ptr buffer = std::make_unique(bufferCapacity); + // request regkey binary content + if (RegQueryValueExW(hKey, NonLocalizable::RegVirtualDesktopIds, 0, nullptr, buffer.get(), &bufferCapacity) != ERROR_SUCCESS) + { + return false; + } + const size_t guidSize = sizeof(GUID); + std::vector temp; + temp.reserve(bufferCapacity / guidSize); + for (size_t i = 0; i < bufferCapacity; i += guidSize) + { + GUID* guid = reinterpret_cast(buffer.get() + i); + temp.push_back(*guid); + } + ids = std::move(temp); + return true; + } + + HKEY OpenVirtualDesktopsRegKey() + { + HKEY hKey{ nullptr }; + if (RegOpenKeyEx(HKEY_CURRENT_USER, NonLocalizable::RegKeyVirtualDesktops, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) + { + return hKey; + } + return nullptr; + } + + HKEY GetVirtualDesktopsRegKey() + { + static wil::unique_hkey virtualDesktopsKey{ OpenVirtualDesktopsRegKey() }; + return virtualDesktopsKey.get(); + } + bool GetVirtualDesktopIds(std::vector& ids) + { + return GetVirtualDesktopIds(GetVirtualDesktopsRegKey(), ids); + } + + bool GetVirtualDesktopIds(std::vector& ids) + { + std::vector guids{}; + if (GetVirtualDesktopIds(guids)) + { + for (auto& guid : guids) + { + wil::unique_cotaskmem_string guidString; + if (SUCCEEDED(StringFromCLSID(guid, &guidString))) + { + ids.push_back(guidString.get()); + } + } + return true; + } + return false; + } + + bool GetCurrentVirtualDesktopId(GUID* desktopId) + { + // BODGY + // On newer Windows builds, the current virtual desktop is persisted to + // a totally different reg key. Look there first. + if (NewGetCurrentDesktopId(desktopId)) + { + return true; + } + + // Explorer persists current virtual desktop identifier to registry on a per session basis, but only + // after first virtual desktop switch happens. If the user hasn't switched virtual desktops in this + // session, value in registry will be empty. + if (GetDesktopIdFromCurrentSession(desktopId)) + { + return true; + } + // Fallback scenario is to get array of virtual desktops stored in registry, but not kept per session. + // Note that we are taking first element from virtual desktop array, which is primary desktop. + // If user has more than one virtual desktop, previous function should return correct value, as desktop + // switch occurred in current session. + else + { + std::vector ids{}; + if (GetVirtualDesktopIds(ids) && ids.size() > 0) + { + *desktopId = ids[0]; + return true; + } + } + return false; + } +} diff --git a/src/cascadia/WindowsTerminal/VirtualDesktopUtils.h b/src/cascadia/WindowsTerminal/VirtualDesktopUtils.h new file mode 100644 index 000000000..cd0dd56fa --- /dev/null +++ b/src/cascadia/WindowsTerminal/VirtualDesktopUtils.h @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// A helper function for determining the GUID of the current Virtual Desktop. + +#pragma once + +namespace VirtualDesktopUtils +{ + bool GetCurrentVirtualDesktopId(GUID* desktopId); +} diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index fba24855f..806c83c0b 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -48,6 +48,7 @@ + @@ -57,6 +58,7 @@ +