diff --git a/.github/actions/spelling/dictionary/apis.txt b/.github/actions/spelling/dictionary/apis.txt
index 68137d69296..f5812a01b07 100644
--- a/.github/actions/spelling/dictionary/apis.txt
+++ b/.github/actions/spelling/dictionary/apis.txt
@@ -77,6 +77,7 @@ NOASYNC
NOCHANGEDIR
NOPROGRESS
NOREDIRECTIONBITMAP
+NOREPEAT
ntprivapi
oaidl
ocidl
diff --git a/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj b/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj
index 519a9c69acc..34f70956ae8 100644
--- a/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj
+++ b/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj
@@ -25,6 +25,9 @@
Monarch.idl
+
+ Monarch.idl
+
Peasant.idl
@@ -54,6 +57,9 @@
Monarch.idl
+
+ Monarch.idl
+
Peasant.idl
diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp
index 6945777815a..66cbe6ad21b 100644
--- a/src/cascadia/Remoting/Monarch.cpp
+++ b/src/cascadia/Remoting/Monarch.cpp
@@ -375,7 +375,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
continue;
}
- if (peasant.WindowName() == L"_quake")
+ if (peasant.WindowName() == QuakeWindowName)
{
// The _quake window should never be treated as the MRU window.
// Skip it if we see it. Users can still target it with `wt -w
@@ -686,4 +686,51 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
}
+
+ // Method Description:
+ // - Attempt to summon a window. `args` contains information about which
+ // window we should try to summon:
+ // * if a WindowName is provided, we'll try to find a window with exactly
+ // that name, and fail if there isn't one.
+ // - Calls Peasant::Summon on the matching peasant (which might be an RPC call)
+ // - This should only ever be called by the WindowManager in the monarch
+ // process itself. The monarch is the one registering for global hotkeys,
+ // so it's the one calling this method.
+ // Arguments:
+ // - args: contains information about the window that should be summoned.
+ // Return Value:
+ // -
+ // - Sets args.FoundMatch when a window matching args is found successfully.
+ void Monarch::SummonWindow(const Remoting::SummonWindowSelectionArgs& args)
+ {
+ const auto searchedForName{ args.WindowName() };
+ try
+ {
+ args.FoundMatch(false);
+ uint64_t windowId = 0;
+ // If no name was provided, then just summon the MRU window.
+ if (searchedForName.empty())
+ {
+ windowId = _getMostRecentPeasantID(true);
+ }
+ else
+ {
+ // Try to find a peasant that currently has this name
+ windowId = _lookupPeasantIdForName(searchedForName);
+ }
+ if (auto targetPeasant{ _getPeasant(windowId) })
+ {
+ targetPeasant.Summon();
+ args.FoundMatch(true);
+ }
+ }
+ catch (...)
+ {
+ LOG_CAUGHT_EXCEPTION();
+ TraceLoggingWrite(g_hRemotingProvider,
+ "Monarch_SummonWindow_Failed",
+ TraceLoggingWideString(searchedForName.c_str(), "searchedForName", "The name of the window we tried to summon"),
+ TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
+ }
+ }
}
diff --git a/src/cascadia/Remoting/Monarch.h b/src/cascadia/Remoting/Monarch.h
index 7996934fd0e..e7f2da0da62 100644
--- a/src/cascadia/Remoting/Monarch.h
+++ b/src/cascadia/Remoting/Monarch.h
@@ -49,6 +49,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::Microsoft::Terminal::Remoting::ProposeCommandlineResult ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
+ void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
diff --git a/src/cascadia/Remoting/Monarch.idl b/src/cascadia/Remoting/Monarch.idl
index 836cb7ee9d4..ca62cdc0440 100644
--- a/src/cascadia/Remoting/Monarch.idl
+++ b/src/cascadia/Remoting/Monarch.idl
@@ -18,6 +18,17 @@ namespace Microsoft.Terminal.Remoting
Boolean ShouldCreateWindow { get; }; // If you name this `CreateWindow`, the compiler will explode
}
+ [default_interface] runtimeclass SummonWindowSelectionArgs {
+ SummonWindowSelectionArgs();
+ SummonWindowSelectionArgs(String windowName);
+ String WindowName;
+ // TODO GH#8888 Other options:
+ // * CurrentDesktop
+ // * CurrentMonitor
+
+ Boolean FoundMatch;
+ }
+
[default_interface] runtimeclass Monarch {
Monarch();
@@ -25,6 +36,7 @@ namespace Microsoft.Terminal.Remoting
UInt64 AddPeasant(IPeasant peasant);
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args);
void HandleActivatePeasant(WindowActivatedArgs args);
+ void SummonWindow(SummonWindowSelectionArgs args);
event Windows.Foundation.TypedEventHandler FindTargetWindowRequested;
};
diff --git a/src/cascadia/Remoting/Peasant.cpp b/src/cascadia/Remoting/Peasant.cpp
index c47cf1e98f2..990d1d591b0 100644
--- a/src/cascadia/Remoting/Peasant.cpp
+++ b/src/cascadia/Remoting/Peasant.cpp
@@ -117,6 +117,25 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return _lastActivatedArgs;
}
+ // Method Description:
+ // - Summon this peasant to become the active window. Currently, it just
+ // causes the peasant to become the active window wherever the window
+ // already was.
+ // - Will raise a SummonRequested event to ask the hosting window to handle for us.
+ // Arguments:
+ // -
+ // Return Value:
+ // -
+ void Peasant::Summon()
+ {
+ _SummonRequestedHandlers(*this, nullptr);
+
+ TraceLoggingWrite(g_hRemotingProvider,
+ "Peasant_Summon",
+ TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
+ TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
+ }
+
// Method Description:
// - Tell this window to display it's window ID. We'll raise a
// DisplayWindowIdRequested event, which will get handled in the AppHost,
diff --git a/src/cascadia/Remoting/Peasant.h b/src/cascadia/Remoting/Peasant.h
index 67785c95bd1..72c672c2e44 100644
--- a/src/cascadia/Remoting/Peasant.h
+++ b/src/cascadia/Remoting/Peasant.h
@@ -23,6 +23,8 @@ 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 RequestIdentifyWindows();
void DisplayWindowId();
void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
@@ -37,6 +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);
private:
Peasant(const uint64_t testPID);
diff --git a/src/cascadia/Remoting/Peasant.idl b/src/cascadia/Remoting/Peasant.idl
index 583a494a6b9..6ff404b73fe 100644
--- a/src/cascadia/Remoting/Peasant.idl
+++ b/src/cascadia/Remoting/Peasant.idl
@@ -40,17 +40,20 @@ namespace Microsoft.Terminal.Remoting
Boolean ExecuteCommandline(CommandlineArgs args);
void ActivateWindow(WindowActivatedArgs args);
WindowActivatedArgs GetLastActivatedArgs();
- String WindowName { get; };
- void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
+
void DisplayWindowId(); // Tells us to display its own ID (which causes a DisplayWindowIdRequested to be raised)
+ String WindowName { get; };
+ void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
+ void Summon();
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;
};
[default_interface] runtimeclass Peasant : IPeasant
diff --git a/src/cascadia/Remoting/SummonWindowSelectionArgs.cpp b/src/cascadia/Remoting/SummonWindowSelectionArgs.cpp
new file mode 100644
index 00000000000..6db4cad3304
--- /dev/null
+++ b/src/cascadia/Remoting/SummonWindowSelectionArgs.cpp
@@ -0,0 +1,5 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+#include "pch.h"
+#include "SummonWindowSelectionArgs.h"
+#include "SummonWindowSelectionArgs.g.cpp"
diff --git a/src/cascadia/Remoting/SummonWindowSelectionArgs.h b/src/cascadia/Remoting/SummonWindowSelectionArgs.h
new file mode 100644
index 00000000000..4aec6488f41
--- /dev/null
+++ b/src/cascadia/Remoting/SummonWindowSelectionArgs.h
@@ -0,0 +1,40 @@
+/*++
+Copyright (c) Microsoft Corporation
+Licensed under the MIT license.
+
+Class Name:
+- SummonWindowSelectionArgs.h
+
+Abstract:
+- This is a helper class for determining which window a should be summoned when
+ a global hotkey is pressed. Parameters from a GlobalSummon action will be
+ filled in here. The Monarch will use these to find the window that matches
+ these args, and Summon() that Peasant.
+- When the monarch finds a match, it will set FoundMatch to true. If it doesn't,
+ then the Monarch window might need to create a new window matching these args
+ instead.
+--*/
+
+#pragma once
+
+#include "SummonWindowSelectionArgs.g.h"
+#include "../cascadia/inc/cppwinrt_utils.h"
+
+namespace winrt::Microsoft::Terminal::Remoting::implementation
+{
+ struct SummonWindowSelectionArgs : public SummonWindowSelectionArgsT
+ {
+ public:
+ SummonWindowSelectionArgs() = default;
+ SummonWindowSelectionArgs(winrt::hstring name) :
+ _WindowName{ name } {};
+
+ WINRT_PROPERTY(winrt::hstring, WindowName);
+ WINRT_PROPERTY(bool, FoundMatch, false);
+ };
+}
+
+namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
+{
+ BASIC_FACTORY(SummonWindowSelectionArgs);
+}
diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp
index f76bfd47821..f96262dad0a 100644
--- a/src/cascadia/Remoting/WindowManager.cpp
+++ b/src/cascadia/Remoting/WindowManager.cpp
@@ -247,6 +247,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// window, and when the current monarch dies.
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
+
+ _BecameMonarchHandlers(*this, nullptr);
}
bool WindowManager::_areWeTheKing()
@@ -478,4 +480,18 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
_FindTargetWindowRequestedHandlers(sender, args);
}
+
+ bool WindowManager::IsMonarch()
+ {
+ return _isKing;
+ }
+
+ void WindowManager::SummonWindow(const Remoting::SummonWindowSelectionArgs& args)
+ {
+ // We should only ever get called when we are the monarch, because only
+ // the monarch ever registers for the global hotkey. So the monarch is
+ // the only window that will be calling this.
+ _monarch.SummonWindow(args);
+ }
+
}
diff --git a/src/cascadia/Remoting/WindowManager.h b/src/cascadia/Remoting/WindowManager.h
index 8e5678369b2..85451884b90 100644
--- a/src/cascadia/Remoting/WindowManager.h
+++ b/src/cascadia/Remoting/WindowManager.h
@@ -37,8 +37,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
bool ShouldCreateWindow();
winrt::Microsoft::Terminal::Remoting::Peasant CurrentWindow();
+ bool IsMonarch();
+ void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
+ TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
bool _shouldCreateWindow{ false };
diff --git a/src/cascadia/Remoting/WindowManager.idl b/src/cascadia/Remoting/WindowManager.idl
index 3099f1128b5..547b96f9aa5 100644
--- a/src/cascadia/Remoting/WindowManager.idl
+++ b/src/cascadia/Remoting/WindowManager.idl
@@ -10,6 +10,9 @@ namespace Microsoft.Terminal.Remoting
void ProposeCommandline(CommandlineArgs args);
Boolean ShouldCreateWindow { get; };
IPeasant CurrentWindow();
+ Boolean IsMonarch { get; };
+ void SummonWindow(SummonWindowSelectionArgs args);
event Windows.Foundation.TypedEventHandler FindTargetWindowRequested;
+ event Windows.Foundation.TypedEventHandler BecameMonarch;
};
}
diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp
index 681aaeadd49..04c037f27ed 100644
--- a/src/cascadia/TerminalApp/AppActionHandlers.cpp
+++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp
@@ -771,4 +771,23 @@ namespace winrt::TerminalApp::implementation
args.Handled(true);
}
+
+ void TerminalPage::_HandleGlobalSummon(const IInspectable& /*sender*/,
+ const ActionEventArgs& args)
+ {
+ // Manually return false. These shouldn't ever get here, except for when
+ // we fail to register for the global hotkey. In that case, returning
+ // false here will let the underlying terminal still process the key, as
+ // if it wasn't bound at all.
+ args.Handled(false);
+ }
+ void TerminalPage::_HandleQuakeMode(const IInspectable& /*sender*/,
+ const ActionEventArgs& args)
+ {
+ // Manually return false. These shouldn't ever get here, except for when
+ // we fail to register for the global hotkey. In that case, returning
+ // false here will let the underlying terminal still process the key, as
+ // if it wasn't bound at all.
+ args.Handled(false);
+ }
}
diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp
index 3cc669badab..e8229157b7d 100644
--- a/src/cascadia/TerminalApp/AppLogic.cpp
+++ b/src/cascadia/TerminalApp/AppLogic.cpp
@@ -997,7 +997,7 @@ namespace winrt::TerminalApp::implementation
CATCH_LOG();
// Method Description:
- // - Reloads the settings from the profile.json.
+ // - Reloads the settings from the settings.json file.
void AppLogic::_ReloadSettings()
{
// Attempt to load our settings.
@@ -1027,6 +1027,8 @@ namespace winrt::TerminalApp::implementation
_ApplyStartupTaskStateChange();
Jumplist::UpdateJumplist(_settings);
+
+ _SettingsChangedHandlers(*this, nullptr);
}
// Method Description:
@@ -1409,6 +1411,11 @@ namespace winrt::TerminalApp::implementation
return _root ? _root->AlwaysOnTop() : false;
}
+ Windows::Foundation::Collections::IMap AppLogic::GlobalHotkeys()
+ {
+ return _settings.GlobalSettings().KeyMap().GlobalHotkeys();
+ }
+
void AppLogic::IdentifyWindow()
{
if (_root)
diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h
index 54c1a206944..951185f473d 100644
--- a/src/cascadia/TerminalApp/AppLogic.h
+++ b/src/cascadia/TerminalApp/AppLogic.h
@@ -90,8 +90,11 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::Foundation::IAsyncOperation ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
+ Windows::Foundation::Collections::IMap GlobalHotkeys();
+
// -------------------------------- WinRT Events ---------------------------------
TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::ElementTheme);
+ TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
bool _isUwp{ false };
diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl
index 6bbd2a5d1e4..bbad80254d8 100644
--- a/src/cascadia/TerminalApp/AppLogic.idl
+++ b/src/cascadia/TerminalApp/AppLogic.idl
@@ -71,6 +71,8 @@ namespace TerminalApp
FindTargetWindowResult FindTargetWindow(String[] args);
+ Windows.Foundation.Collections.IMap GlobalHotkeys();
+
// See IDialogPresenter and TerminalPage's DialogPresenter for more
// information.
Windows.Foundation.IAsyncOperation ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog);
@@ -86,6 +88,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler SetTaskbarProgress;
event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler RenameWindowRequested;
+ event Windows.Foundation.TypedEventHandler SettingsChanged;
event Windows.Foundation.TypedEventHandler IsQuakeWindowChanged;
}
}
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 9094d7cc5db..62c0f8e2dd4 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -16,6 +16,7 @@
#include "DebugTapConnection.h"
#include "SettingsTab.h"
#include "RenameWindowRequestedArgs.g.cpp"
+#include "../inc/WindowingBehavior.h"
using namespace winrt;
using namespace winrt::Windows::Foundation::Collections;
@@ -42,8 +43,6 @@ namespace winrt
using IInspectable = Windows::Foundation::IInspectable;
}
-static constexpr std::wstring_view QuakeWindowName{ L"_quake" };
-
namespace winrt::TerminalApp::implementation
{
TerminalPage::TerminalPage() :
diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
index 7cdfc96796b..aa7fc7c7d0b 100644
--- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
@@ -57,6 +57,8 @@ static constexpr std::string_view IdentifyWindowKey{ "identifyWindow" };
static constexpr std::string_view IdentifyWindowsKey{ "identifyWindows" };
static constexpr std::string_view RenameWindowKey{ "renameWindow" };
static constexpr std::string_view OpenWindowRenamerKey{ "openWindowRenamer" };
+static constexpr std::string_view GlobalSummonKey{ "globalSummon" };
+static constexpr std::string_view QuakeModeKey{ "quakeMode" };
static constexpr std::string_view ActionKey{ "action" };
@@ -127,6 +129,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ IdentifyWindowsKey, ShortcutAction::IdentifyWindows },
{ RenameWindowKey, ShortcutAction::RenameWindow },
{ OpenWindowRenamerKey, ShortcutAction::OpenWindowRenamer },
+ { GlobalSummonKey, ShortcutAction::GlobalSummon },
+ { QuakeModeKey, ShortcutAction::QuakeMode },
};
using ParseResult = std::tuple>;
@@ -162,6 +166,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::PrevTab, PrevTabArgs::FromJson },
{ ShortcutAction::NextTab, NextTabArgs::FromJson },
{ ShortcutAction::RenameWindow, RenameWindowArgs::FromJson },
+ { ShortcutAction::GlobalSummon, GlobalSummonArgs::FromJson },
+ { ShortcutAction::QuakeMode, GlobalSummonArgs::QuakeModeFromJson },
{ ShortcutAction::Invalid, nullptr },
};
@@ -337,6 +343,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::IdentifyWindows, RS_(L"IdentifyWindowsCommandKey") },
{ ShortcutAction::RenameWindow, RS_(L"ResetWindowNameCommandKey") },
{ ShortcutAction::OpenWindowRenamer, RS_(L"OpenWindowRenamerCommandKey") },
+ { ShortcutAction::GlobalSummon, L"" }, // Intentionally omitted, must be generated by GenerateName
+ { ShortcutAction::QuakeMode, RS_(L"QuakeModeCommandKey") },
};
}();
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
index aebc2f4ecdd..9565d3d8132 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
@@ -29,6 +29,7 @@
#include "PrevTabArgs.g.cpp"
#include "NextTabArgs.g.cpp"
#include "RenameWindowArgs.g.cpp"
+#include "GlobalSummonArgs.g.cpp"
#include
@@ -582,4 +583,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
return RS_(L"ResetWindowNameCommandKey");
}
+
+ winrt::hstring GlobalSummonArgs::GenerateName() const
+ {
+ std::wstringstream ss;
+ ss << std::wstring_view(RS_(L"GlobalSummonCommandKey"));
+
+ // "Summon the Terminal window"
+ // "Summon the Terminal window, name:\"{_Name}\""
+ if (!_Name.empty())
+ {
+ ss << L", name: ";
+ ss << std::wstring_view(_Name);
+ }
+ return winrt::hstring{ ss.str() };
+ }
}
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h
index 68e77ef99f4..68132691604 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.h
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h
@@ -31,10 +31,12 @@
#include "PrevTabArgs.g.h"
#include "NextTabArgs.g.h"
#include "RenameWindowArgs.g.h"
+#include "GlobalSummonArgs.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include "JsonUtils.h"
#include "TerminalWarnings.h"
+#include "../inc/WindowingBehavior.h"
#include "TerminalSettingsSerializationHelpers.h"
@@ -1037,6 +1039,49 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return *copy;
}
};
+
+ struct GlobalSummonArgs : public GlobalSummonArgsT
+ {
+ GlobalSummonArgs() = default;
+ WINRT_PROPERTY(winrt::hstring, Name, L"");
+
+ static constexpr std::string_view NameKey{ "name" };
+
+ public:
+ hstring GenerateName() const;
+
+ bool Equals(const IActionArgs& other)
+ {
+ if (auto otherAsUs = other.try_as(); otherAsUs)
+ {
+ return otherAsUs->_Name == _Name;
+ }
+ return false;
+ };
+ static FromJsonResult FromJson(const Json::Value& json)
+ {
+ // LOAD BEARING: Not using make_self here _will_ break you in the future!
+ auto args = winrt::make_self();
+ JsonUtils::GetValueForKey(json, NameKey, args->_Name);
+ return { *args, {} };
+ }
+ IActionArgs Copy() const
+ {
+ auto copy{ winrt::make_self() };
+ copy->_Name = _Name;
+ return *copy;
+ }
+ // SPECIAL! This deserializer creates a GlobalSummonArgs with the
+ // default values for quakeMode
+ static FromJsonResult QuakeModeFromJson(const Json::Value& /*json*/)
+ {
+ // LOAD BEARING: Not using make_self here _will_ break you in the future!
+ auto args = winrt::make_self();
+ args->_Name = QuakeWindowName;
+ return { *args, {} };
+ }
+ };
+
}
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl
index cb666d9b826..ca29300fe52 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl
@@ -252,4 +252,9 @@ namespace Microsoft.Terminal.Settings.Model
{
String Name { get; };
};
+
+ [default_interface] runtimeclass GlobalSummonArgs : IActionArgs
+ {
+ String Name { get; };
+ };
}
diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
index cf32e5f36ec..bc710a756b9 100644
--- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
+++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
@@ -72,4 +72,6 @@
ON_ALL_ACTIONS(IdentifyWindow) \
ON_ALL_ACTIONS(IdentifyWindows) \
ON_ALL_ACTIONS(RenameWindow) \
- ON_ALL_ACTIONS(OpenWindowRenamer)
+ ON_ALL_ACTIONS(OpenWindowRenamer) \
+ ON_ALL_ACTIONS(GlobalSummon) \
+ ON_ALL_ACTIONS(QuakeMode)
diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp
index d9d1427d37a..cc98e3d4a14 100644
--- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp
+++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp
@@ -7,6 +7,7 @@
#include "../../inc/DefaultSettings.h"
#include "JsonUtils.h"
#include "TerminalSettingsSerializationHelpers.h"
+#include "KeyChordSerialization.h"
#include "GlobalAppSettings.g.cpp"
diff --git a/src/cascadia/TerminalSettingsModel/KeyMapping.cpp b/src/cascadia/TerminalSettingsModel/KeyMapping.cpp
index 880a6b34d4a..3bd93cfe59f 100644
--- a/src/cascadia/TerminalSettingsModel/KeyMapping.cpp
+++ b/src/cascadia/TerminalSettingsModel/KeyMapping.cpp
@@ -141,4 +141,32 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return keyModifiers;
}
+
+ // Method Description:
+ // - Build a map of all the globalSummon actions.
+ // - quakeMode actions are included in this, but expanded to the equivalent
+ // set of GlobalSummonArgs
+ // - This is only ever called in two scenarios:
+ // - on becoming the monarch (which only happens once per window)
+ // - when the settings reload (and the cache would inevitably be dirty)
+ // So it's perfectly reasonable to not cache these results.
+ // Arguments:
+ // -
+ // Return Value:
+ // - a map of KeyChord -> ActionAndArgs containing all globally bindable actions.
+ Windows::Foundation::Collections::IMap KeyMapping::GlobalHotkeys()
+ {
+ std::unordered_map justGlobals;
+
+ for (const auto& [k, v] : _keyShortcuts)
+ {
+ if (v.Action() == ShortcutAction::GlobalSummon || v.Action() == ShortcutAction::QuakeMode)
+ {
+ justGlobals[k] = v;
+ }
+ }
+
+ return winrt::single_threaded_map(std::move(justGlobals));
+ }
+
}
diff --git a/src/cascadia/TerminalSettingsModel/KeyMapping.h b/src/cascadia/TerminalSettingsModel/KeyMapping.h
index ce5c811e389..eea978b59a6 100644
--- a/src/cascadia/TerminalSettingsModel/KeyMapping.h
+++ b/src/cascadia/TerminalSettingsModel/KeyMapping.h
@@ -69,6 +69,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
std::vector LayerJson(const Json::Value& json);
Json::Value ToJson();
+ Windows::Foundation::Collections::IMap GlobalHotkeys();
+
private:
std::unordered_map _keyShortcuts;
std::vector> _keyShortcutsByInsertionOrder;
diff --git a/src/cascadia/TerminalSettingsModel/KeyMapping.idl b/src/cascadia/TerminalSettingsModel/KeyMapping.idl
index 540c74dea50..1b6886acded 100644
--- a/src/cascadia/TerminalSettingsModel/KeyMapping.idl
+++ b/src/cascadia/TerminalSettingsModel/KeyMapping.idl
@@ -35,5 +35,7 @@ namespace Microsoft.Terminal.Settings.Model
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action);
Microsoft.Terminal.Control.KeyChord GetKeyBindingForActionWithArgs(ActionAndArgs actionAndArgs);
+
+ Windows.Foundation.Collections.IMap GlobalHotkeys();
}
}
diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
index 2f1ed50adf5..8f8aece2a2f 100644
--- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
@@ -1,17 +1,17 @@
-
@@ -391,6 +391,12 @@
Rename window...
+
+ Summon the Terminal window
+
+
+ Summon Quake window
+
Microsoft Corporation
Paired with `InboxWindowsConsoleName`, this is the application author... which is us: Microsoft.
@@ -399,4 +405,4 @@
Windows Console Host
Name describing the usage of the classic windows console as the terminal UI. (`conhost.exe`)
-
\ No newline at end of file
+
diff --git a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp
index 1e3bd4f2309..694dcef8fb0 100644
--- a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp
+++ b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp
@@ -60,11 +60,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{}; };
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);
};
class RemotingTests
@@ -107,6 +109,11 @@ namespace RemotingUnitTests
TEST_METHOD(TestRenameSameNameAsAnother);
TEST_METHOD(TestRenameSameNameAsADeadPeasant);
+ TEST_METHOD(TestSummonMostRecentWindow);
+ TEST_METHOD(TestSummonNamedWindow);
+ TEST_METHOD(TestSummonNamedDeadWindow);
+ TEST_METHOD(TestSummonMostRecentDeadWindow);
+
TEST_CLASS_SETUP(ClassSetup)
{
return true;
@@ -468,7 +475,6 @@ namespace RemotingUnitTests
VERIFY_ARE_EQUAL(false, (bool)result.Id());
}
}
-
void RemotingTests::ProposeCommandlineCurrentWindow()
{
Log::Comment(L"Test proposing a commandline for the current window (ID=0)");
@@ -549,7 +555,6 @@ namespace RemotingUnitTests
VERIFY_ARE_EQUAL(false, (bool)result.Id());
}
}
-
void RemotingTests::ProposeCommandlineNonExistentWindow()
{
Log::Comment(L"Test proposing a commandline for an ID that doesn't have a current peasant");
@@ -1610,4 +1615,334 @@ namespace RemotingUnitTests
VERIFY_ARE_EQUAL(p1->GetID(), m0->_lookupPeasantIdForName(L"two"));
}
+ void RemotingTests::TestSummonMostRecentWindow()
+ {
+ Log::Comment(L"Attempt to summon the most recent window");
+
+ const winrt::guid guid1{ Utils::GuidFromString(L"{11111111-1111-1111-1111-111111111111}") };
+
+ const auto monarch0PID = 12345u;
+ const auto peasant1PID = 23456u;
+ const auto peasant2PID = 34567u;
+
+ 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));
+
+ VERIFY_IS_NOT_NULL(m0);
+ VERIFY_IS_NOT_NULL(p1);
+ VERIFY_IS_NOT_NULL(p2);
+ p1->WindowName(L"one");
+ p2->WindowName(L"two");
+
+ VERIFY_ARE_EQUAL(0, p1->GetID());
+ VERIFY_ARE_EQUAL(0, p2->GetID());
+
+ m0->AddPeasant(*p1);
+ m0->AddPeasant(*p2);
+
+ VERIFY_ARE_EQUAL(1, p1->GetID());
+ VERIFY_ARE_EQUAL(2, p2->GetID());
+
+ VERIFY_ARE_EQUAL(2u, m0->_peasants.size());
+
+ bool p1ExpectedToBeSummoned = false;
+ bool p2ExpectedToBeSummoned = 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);
+ });
+
+ {
+ Log::Comment(L"Activate the first peasant, first desktop");
+ Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(),
+ guid1,
+ winrt::clock().now() };
+ p1->ActivateWindow(activatedArgs);
+ }
+ {
+ Log::Comment(L"Activate the second peasant, first desktop");
+ Remoting::WindowActivatedArgs activatedArgs{ p2->GetID(),
+ guid1,
+ winrt::clock().now() };
+ p2->ActivateWindow(activatedArgs);
+ }
+
+ p2ExpectedToBeSummoned = true;
+ Remoting::SummonWindowSelectionArgs args;
+ // Without setting the WindowName, SummonWindowSelectionArgs defaults to
+ // the MRU window
+ Log::Comment(L"Summon the MRU window, which is window two");
+ m0->SummonWindow(args);
+ VERIFY_IS_TRUE(args.FoundMatch());
+
+ {
+ Log::Comment(L"Activate the first peasant, first desktop");
+ Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(),
+ guid1,
+ winrt::clock().now() };
+ p1->ActivateWindow(activatedArgs);
+ }
+
+ Log::Comment(L"Now that one is the MRU, summon it");
+ p2ExpectedToBeSummoned = false;
+ p1ExpectedToBeSummoned = true;
+ args.FoundMatch(false);
+ m0->SummonWindow(args);
+ VERIFY_IS_TRUE(args.FoundMatch());
+ }
+
+ void RemotingTests::TestSummonNamedWindow()
+ {
+ Log::Comment(L"Attempt to summon a window by name. When there isn't a "
+ L"window with that name, set FoundMatch to false, so the "
+ L"caller can handle that case.");
+
+ const auto monarch0PID = 12345u;
+ const auto peasant1PID = 23456u;
+ const auto peasant2PID = 34567u;
+
+ 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));
+
+ VERIFY_IS_NOT_NULL(m0);
+ VERIFY_IS_NOT_NULL(p1);
+ VERIFY_IS_NOT_NULL(p2);
+ p1->WindowName(L"one");
+ p2->WindowName(L"two");
+
+ VERIFY_ARE_EQUAL(0, p1->GetID());
+ VERIFY_ARE_EQUAL(0, p2->GetID());
+
+ m0->AddPeasant(*p1);
+ m0->AddPeasant(*p2);
+
+ VERIFY_ARE_EQUAL(1, p1->GetID());
+ VERIFY_ARE_EQUAL(2, p2->GetID());
+
+ VERIFY_ARE_EQUAL(2u, m0->_peasants.size());
+
+ bool p1ExpectedToBeSummoned = false;
+ bool p2ExpectedToBeSummoned = 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);
+ });
+
+ Remoting::SummonWindowSelectionArgs args;
+
+ Log::Comment(L"Summon window two by name");
+ p2ExpectedToBeSummoned = true;
+ args.WindowName(L"two");
+ m0->SummonWindow(args);
+ VERIFY_IS_TRUE(args.FoundMatch());
+
+ Log::Comment(L"Summon window one by name");
+ p2ExpectedToBeSummoned = false;
+ p1ExpectedToBeSummoned = true;
+ args.FoundMatch(false);
+ args.WindowName(L"one");
+ m0->SummonWindow(args);
+ VERIFY_IS_TRUE(args.FoundMatch());
+
+ Log::Comment(L"Fail to summon window three by name");
+ p1ExpectedToBeSummoned = false;
+ args.FoundMatch(false);
+ args.WindowName(L"three");
+ m0->SummonWindow(args);
+ VERIFY_IS_FALSE(args.FoundMatch());
+ }
+
+ void RemotingTests::TestSummonNamedDeadWindow()
+ {
+ Log::Comment(L"Attempt to summon a dead window by name. This will fail, but not crash.");
+
+ const auto monarch0PID = 12345u;
+ const auto peasant1PID = 23456u;
+ const auto peasant2PID = 34567u;
+
+ 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));
+
+ VERIFY_IS_NOT_NULL(m0);
+ VERIFY_IS_NOT_NULL(p1);
+ VERIFY_IS_NOT_NULL(p2);
+ p1->WindowName(L"one");
+ p2->WindowName(L"two");
+
+ VERIFY_ARE_EQUAL(0, p1->GetID());
+ VERIFY_ARE_EQUAL(0, p2->GetID());
+
+ m0->AddPeasant(*p1);
+ m0->AddPeasant(*p2);
+
+ VERIFY_ARE_EQUAL(1, p1->GetID());
+ VERIFY_ARE_EQUAL(2, p2->GetID());
+
+ VERIFY_ARE_EQUAL(2u, m0->_peasants.size());
+
+ bool p1ExpectedToBeSummoned = false;
+ bool p2ExpectedToBeSummoned = 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);
+ });
+
+ Remoting::SummonWindowSelectionArgs args;
+
+ Log::Comment(L"Summon window two by name");
+ p2ExpectedToBeSummoned = true;
+ args.WindowName(L"two");
+ m0->SummonWindow(args);
+ VERIFY_IS_TRUE(args.FoundMatch());
+
+ Log::Comment(L"Summon window one by name");
+ p2ExpectedToBeSummoned = false;
+ p1ExpectedToBeSummoned = true;
+ args.FoundMatch(false);
+ args.WindowName(L"one");
+ m0->SummonWindow(args);
+ VERIFY_IS_TRUE(args.FoundMatch());
+
+ Log::Comment(L"Kill peasant one.");
+ RemotingTests::_killPeasant(m0, p1->GetID());
+
+ Log::Comment(L"Fail to summon window one by name");
+ p1ExpectedToBeSummoned = false;
+ args.FoundMatch(false);
+ args.WindowName(L"one");
+ m0->SummonWindow(args);
+ VERIFY_IS_FALSE(args.FoundMatch());
+ }
+
+ void RemotingTests::TestSummonMostRecentDeadWindow()
+ {
+ Log::Comment(L"Attempt to summon the MRU window, when the MRU window "
+ L"has died. This will fall back to the next MRU window.");
+
+ const winrt::guid guid1{ Utils::GuidFromString(L"{11111111-1111-1111-1111-111111111111}") };
+
+ const auto monarch0PID = 12345u;
+ const auto peasant1PID = 23456u;
+ const auto peasant2PID = 34567u;
+
+ 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));
+
+ VERIFY_IS_NOT_NULL(m0);
+ VERIFY_IS_NOT_NULL(p1);
+ VERIFY_IS_NOT_NULL(p2);
+ p1->WindowName(L"one");
+ p2->WindowName(L"two");
+
+ VERIFY_ARE_EQUAL(0, p1->GetID());
+ VERIFY_ARE_EQUAL(0, p2->GetID());
+
+ m0->AddPeasant(*p1);
+ m0->AddPeasant(*p2);
+
+ VERIFY_ARE_EQUAL(1, p1->GetID());
+ VERIFY_ARE_EQUAL(2, p2->GetID());
+
+ VERIFY_ARE_EQUAL(2u, m0->_peasants.size());
+
+ bool p1ExpectedToBeSummoned = false;
+ bool p2ExpectedToBeSummoned = 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);
+ });
+
+ {
+ Log::Comment(L"Activate the first peasant, first desktop");
+ Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(),
+ guid1,
+ winrt::clock().now() };
+ p1->ActivateWindow(activatedArgs);
+ }
+ {
+ Log::Comment(L"Activate the second peasant, first desktop");
+ Remoting::WindowActivatedArgs activatedArgs{ p2->GetID(),
+ guid1,
+ winrt::clock().now() };
+ p2->ActivateWindow(activatedArgs);
+ }
+
+ p2ExpectedToBeSummoned = true;
+ Remoting::SummonWindowSelectionArgs args;
+ // Without setting the WindowName, SummonWindowSelectionArgs defaults to
+ // the MRU window
+ Log::Comment(L"Summon the MRU window, which is window two");
+ m0->SummonWindow(args);
+ VERIFY_IS_TRUE(args.FoundMatch());
+
+ {
+ Log::Comment(L"Activate the first peasant, first desktop");
+ Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(),
+ guid1,
+ winrt::clock().now() };
+ p1->ActivateWindow(activatedArgs);
+ }
+
+ Log::Comment(L"Now that one is the MRU, summon it");
+ p2ExpectedToBeSummoned = false;
+ p1ExpectedToBeSummoned = true;
+ args.FoundMatch(false);
+ m0->SummonWindow(args);
+ VERIFY_IS_TRUE(args.FoundMatch());
+
+ Log::Comment(L"Kill peasant one.");
+ RemotingTests::_killPeasant(m0, p1->GetID());
+
+ Log::Comment(L"We now expect to summon two, since the MRU peasant (one) is actually dead.");
+ p2ExpectedToBeSummoned = true;
+ p1ExpectedToBeSummoned = false;
+ args.FoundMatch(false);
+ m0->SummonWindow(args);
+ VERIFY_IS_TRUE(args.FoundMatch());
+ }
+
}
diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp
index b25e96ece22..f4054fbb7d4 100644
--- a/src/cascadia/WindowsTerminal/AppHost.cpp
+++ b/src/cascadia/WindowsTerminal/AppHost.cpp
@@ -6,6 +6,7 @@
#include "../types/inc/Viewport.hpp"
#include "../types/inc/utils.hpp"
#include "../types/inc/User32Utils.hpp"
+#include "../WinRTUtils/inc/WtExeUtils.h"
#include "resource.h"
using namespace winrt::Windows::UI;
@@ -74,8 +75,15 @@ AppHost::AppHost() noexcept :
std::placeholders::_2));
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
_window->WindowActivated({ this, &AppHost::_WindowActivated });
+ _window->HotkeyPressed({ this, &AppHost::_GlobalHotkeyPressed });
_window->SetAlwaysOnTop(_logic.GetInitialAlwaysOnTop());
_window->MakeWindow();
+
+ _windowManager.BecameMonarch({ this, &AppHost::_BecomeMonarch });
+ if (_windowManager.IsMonarch())
+ {
+ _BecomeMonarch(nullptr, nullptr);
+ }
}
AppHost::~AppHost()
@@ -197,6 +205,7 @@ void AppHost::_HandleCommandlineArgs()
// commandline (in the future), it'll trigger this callback, that we'll
// use to send the actions to the app.
peasant.ExecuteCommandlineRequested({ this, &AppHost::_DispatchCommandline });
+ peasant.SummonRequested({ this, &AppHost::_HandleSummon });
peasant.DisplayWindowIdRequested({ this, &AppHost::_DisplayWindowId });
@@ -254,6 +263,7 @@ void AppHost::Initialize()
_logic.SetTaskbarProgress({ this, &AppHost::SetTaskbarProgress });
_logic.IdentifyWindowsRequested({ this, &AppHost::_IdentifyWindowsRequested });
_logic.RenameWindowRequested({ this, &AppHost::_RenameWindowRequested });
+ _logic.SettingsChanged({ this, &AppHost::_HandleSettingsChanged });
_logic.IsQuakeWindowChanged({ this, &AppHost::_IsQuakeWindowChanged });
_window->UpdateTitle(_logic.Title());
@@ -578,6 +588,8 @@ bool AppHost::HasWindow()
void AppHost::_DispatchCommandline(winrt::Windows::Foundation::IInspectable /*sender*/,
Remoting::CommandlineArgs args)
{
+ // Summon the window whenever we dispatch a commandline to it. This will
+ // make it obvious when a new tab/pane is created in a window.
_window->SummonWindow();
_logic.ExecuteCommandline(args.Commandline(), args.CurrentDirectory());
}
@@ -619,6 +631,121 @@ winrt::fire_and_forget AppHost::_WindowActivated()
}
}
+void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*sender*/,
+ const winrt::Windows::Foundation::IInspectable& /*args*/)
+{
+ _setupGlobalHotkeys();
+}
+
+winrt::fire_and_forget AppHost::_setupGlobalHotkeys()
+{
+ // The hotkey MUST be registered on the main thread. It will fail otherwise!
+ co_await winrt::resume_foreground(_logic.GetRoot().Dispatcher(),
+ winrt::Windows::UI::Core::CoreDispatcherPriority::Normal);
+
+ // Remove all the already registered hotkeys before setting up the new ones.
+ _window->UnsetHotkeys(_hotkeys);
+
+ _hotkeyActions = _logic.GlobalHotkeys();
+ _hotkeys.clear();
+ for (const auto& [k, v] : _hotkeyActions)
+ {
+ if (k != nullptr)
+ {
+ _hotkeys.push_back(k);
+ }
+ }
+
+ _window->SetGlobalHotkeys(_hotkeys);
+}
+
+// Method Description:
+// - Called whenever a registered hotkey is pressed. We'll look up the
+// GlobalSummonArgs for the specified hotkey, then dispatch a call to the
+// Monarch with the selection information.
+// - If the monarch finds a match for the window name (or no name was provided),
+// it'll set FoundMatch=true.
+// - If FoundMatch is false, and a name was provided, then we should create a
+// new window with the given name.
+// Arguments:
+// - hotkeyIndex: the index of the entry in _hotkeys that was pressed.
+// Return Value:
+// -
+void AppHost::_GlobalHotkeyPressed(const long hotkeyIndex)
+{
+ if (hotkeyIndex < 0 || static_cast(hotkeyIndex) > _hotkeys.size())
+ {
+ return;
+ }
+ // Lookup the matching keychord
+ Control::KeyChord kc = _hotkeys.at(hotkeyIndex);
+ // Get the stored ActionAndArgs for that chord
+ if (const auto& actionAndArgs{ _hotkeyActions.Lookup(kc) })
+ {
+ if (const auto& summonArgs{ actionAndArgs.Args().try_as() })
+ {
+ Remoting::SummonWindowSelectionArgs args{ summonArgs.Name() };
+
+ _windowManager.SummonWindow(args);
+ if (args.FoundMatch())
+ {
+ // Excellent, the window was found. We have nothing else to do here.
+ }
+ else
+ {
+ // We should make the window ourselves.
+ _createNewTerminalWindow(summonArgs);
+ }
+ }
+ }
+}
+
+// Method Description:
+// - Called when the monarch failed to summon a window for a given set of
+// SummonWindowSelectionArgs. In this case, we should create the specified
+// window ourselves.
+// - This is to support the scenario like `globalSummon(Name="_quake")` being
+// used to summon the window if it already exists, or create it if it doesn't.
+// Arguments:
+// - args: Contains information on how we should name the window
+// Return Value:
+// -
+winrt::fire_and_forget AppHost::_createNewTerminalWindow(Settings::Model::GlobalSummonArgs args)
+{
+ // Hop to the BG thread
+ co_await winrt::resume_background();
+
+ // This will get us the correct exe for dev/preview/release. If you
+ // don't stick this in a local, it'll get mangled by ShellExecute. I
+ // have no idea why.
+ const auto exePath{ GetWtExePath() };
+
+ // If we weren't given a name, then just use new to force the window to be
+ // unnamed.
+ winrt::hstring cmdline{
+ fmt::format(L"-w {}",
+ args.Name().empty() ? L"new" :
+ args.Name())
+ };
+
+ SHELLEXECUTEINFOW seInfo{ 0 };
+ seInfo.cbSize = sizeof(seInfo);
+ seInfo.fMask = SEE_MASK_NOASYNC;
+ seInfo.lpVerb = L"open";
+ seInfo.lpFile = exePath.c_str();
+ seInfo.lpParameters = cmdline.c_str();
+ seInfo.nShow = SW_SHOWNORMAL;
+ LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
+
+ co_return;
+}
+
+void AppHost::_HandleSummon(const winrt::Windows::Foundation::IInspectable& /*sender*/,
+ const winrt::Windows::Foundation::IInspectable& /*args*/)
+{
+ _window->SummonWindow();
+}
+
GUID AppHost::_CurrentDesktopGuid()
{
GUID currentDesktopGuid{ 0 };
@@ -698,6 +825,12 @@ winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Fou
}
}
+void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/,
+ const winrt::Windows::Foundation::IInspectable& /*args*/)
+{
+ _setupGlobalHotkeys();
+}
+
void AppHost::_IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable&,
const winrt::Windows::Foundation::IInspectable&)
{
diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h
index 3c32af7190a..9288ee560df 100644
--- a/src/cascadia/WindowsTerminal/AppHost.h
+++ b/src/cascadia/WindowsTerminal/AppHost.h
@@ -28,6 +28,9 @@ class AppHost
bool _shouldCreateWindow{ false };
winrt::Microsoft::Terminal::Remoting::WindowManager _windowManager{ nullptr };
+ std::vector _hotkeys{};
+ winrt::Windows::Foundation::Collections::IMap _hotkeyActions{ nullptr };
+
void _HandleCommandlineArgs();
void _HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode);
@@ -51,6 +54,13 @@ class AppHost
void _FindTargetWindow(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
+
+ void _BecomeMonarch(const winrt::Windows::Foundation::IInspectable& sender,
+ 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);
+
winrt::fire_and_forget _IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable sender,
const winrt::Windows::Foundation::IInspectable args);
void _DisplayWindowId(const winrt::Windows::Foundation::IInspectable& sender,
@@ -60,6 +70,11 @@ class AppHost
GUID _CurrentDesktopGuid();
+ 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,
+ const winrt::Windows::Foundation::IInspectable& args);
+
void _IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
};
diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp
index 9a7d681908c..7a6d6f4bea9 100644
--- a/src/cascadia/WindowsTerminal/IslandWindow.cpp
+++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp
@@ -15,6 +15,7 @@ using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Hosting;
using namespace winrt::Windows::Foundation::Numerics;
using namespace winrt::Microsoft::Terminal::Settings::Model;
+using namespace winrt::Microsoft::Terminal::Control;
using namespace ::Microsoft::Console::Types;
#define XAML_HOSTING_WINDOW_CLASS_NAME L"CASCADIA_HOSTING_WINDOW_CLASS"
@@ -386,6 +387,11 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
{
switch (message)
{
+ case WM_HOTKEY:
+ {
+ _HotkeyPressedHandlers(static_cast(wparam));
+ return 0;
+ }
case WM_GETMINMAXINFO:
{
_OnGetMinMaxInfo(wparam, lparam);
@@ -402,8 +408,9 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
{
// send focus to the child window
SetFocus(_interopWindowHandle);
- return 0; // eat the message
+ return 0;
}
+ break;
}
case WM_ACTIVATE:
{
@@ -935,6 +942,63 @@ void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled)
}
}
+// Method Description:
+// - Call UnregisterHotKey once for each entry in hotkeyList, to unset all the bound global hotkeys.
+// Arguments:
+// - hotkeyList: a list of hotkeys to unbind
+// Return Value:
+// -
+void IslandWindow::UnsetHotkeys(const std::vector& hotkeyList)
+{
+ TraceLoggingWrite(g_hWindowsTerminalProvider,
+ "UnsetHotkeys",
+ TraceLoggingDescription("Emitted when clearing previously set hotkeys"),
+ TraceLoggingInt64(hotkeyList.size(), "numHotkeys", "The number of hotkeys to unset"),
+ TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
+
+ for (size_t i = 0; i < hotkeyList.size(); i++)
+ {
+ LOG_IF_WIN32_BOOL_FALSE(UnregisterHotKey(_window.get(), static_cast(i)));
+ }
+}
+
+// Method Description:
+// - Call RegisterHotKey once for each entry in hotkeyList, to attempt to
+// register that keybinding as a global hotkey.
+// - When these keys are pressed, we'll get a WM_HOTKEY message with the payload
+// containing the index we registered here.
+// Arguments:
+// - hotkeyList: a list of hotkeys to bind
+// Return Value:
+// -
+void IslandWindow::SetGlobalHotkeys(const std::vector& hotkeyList)
+{
+ TraceLoggingWrite(g_hWindowsTerminalProvider,
+ "SetGlobalHotkeys",
+ TraceLoggingDescription("Emitted when setting hotkeys"),
+ TraceLoggingInt64(hotkeyList.size(), "numHotkeys", "The number of hotkeys to set"),
+ TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
+ int index = 0;
+ for (const auto& hotkey : hotkeyList)
+ {
+ const auto modifiers = hotkey.Modifiers();
+ const auto hotkeyFlags = MOD_NOREPEAT |
+ (WI_IsFlagSet(modifiers, KeyModifiers::Windows) ? MOD_WIN : 0) |
+ (WI_IsFlagSet(modifiers, KeyModifiers::Alt) ? MOD_ALT : 0) |
+ (WI_IsFlagSet(modifiers, KeyModifiers::Ctrl) ? MOD_CONTROL : 0) |
+ (WI_IsFlagSet(modifiers, KeyModifiers::Shift) ? MOD_SHIFT : 0);
+
+ // TODO GH#8888: We should display a warning of some kind if this fails.
+ // This can fail if something else already bound this hotkey.
+ LOG_IF_WIN32_BOOL_FALSE(RegisterHotKey(_window.get(),
+ index,
+ hotkeyFlags,
+ hotkey.Vkey()));
+
+ index++;
+ }
+}
+
// Method Description:
// - Force activate this window. This method will bring us to the foreground and
// activate us. If the window is minimized, it will restore the window. If the
@@ -969,6 +1033,10 @@ winrt::fire_and_forget IslandWindow::SummonWindow()
});
LOG_IF_WIN32_BOOL_FALSE(BringWindowToTop(_window.get()));
LOG_IF_WIN32_BOOL_FALSE(ShowWindow(_window.get(), SW_SHOW));
+
+ // Activate the window too. This will force us to the virtual desktop this
+ // window is on, if it's on another virtual desktop.
+ LOG_LAST_ERROR_IF_NULL(SetActiveWindow(_window.get()));
}
bool IslandWindow::IsQuakeWindow() const noexcept
diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h
index d20bd115556..29897f1112f 100644
--- a/src/cascadia/WindowsTerminal/IslandWindow.h
+++ b/src/cascadia/WindowsTerminal/IslandWindow.h
@@ -38,6 +38,9 @@ class IslandWindow :
void FlashTaskbar();
void SetTaskbarProgress(const size_t state, const size_t progress);
+ void UnsetHotkeys(const std::vector& hotkeyList);
+ void SetGlobalHotkeys(const std::vector& hotkeyList);
+
winrt::fire_and_forget SummonWindow();
bool IsQuakeWindow() const noexcept;
@@ -47,6 +50,7 @@ class IslandWindow :
DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>);
WINRT_CALLBACK(MouseScrolled, winrt::delegate);
WINRT_CALLBACK(WindowActivated, winrt::delegate);
+ WINRT_CALLBACK(HotkeyPressed, winrt::delegate);
protected:
void ForceResize()
diff --git a/src/cascadia/WindowsTerminal/pch.h b/src/cascadia/WindowsTerminal/pch.h
index 8009d455e1a..a8befdf6106 100644
--- a/src/cascadia/WindowsTerminal/pch.h
+++ b/src/cascadia/WindowsTerminal/pch.h
@@ -56,12 +56,14 @@ Module Name:
#include
// Additional headers for various xaml features. We need:
-// * Core so we can resume_foreground
+// * Core so we can resume_foreground with CoreDispatcher
// * Controls for grid
// * Media for ScaleTransform
+// * ApplicationModel for finding the path to wt.exe
#include
#include
#include
+#include
#include
#include
diff --git a/src/cascadia/inc/WindowingBehavior.h b/src/cascadia/inc/WindowingBehavior.h
index bce36f7a693..04260893191 100644
--- a/src/cascadia/inc/WindowingBehavior.h
+++ b/src/cascadia/inc/WindowingBehavior.h
@@ -9,3 +9,5 @@ constexpr int32_t WindowingBehaviorUseNew{ -1 };
constexpr int32_t WindowingBehaviorUseExisting{ -2 };
constexpr int32_t WindowingBehaviorUseAnyExisting{ -3 };
constexpr int32_t WindowingBehaviorUseName{ -4 };
+
+static constexpr std::wstring_view QuakeWindowName{ L"_quake" };