Skip to content

Commit

Permalink
Add globalSummon action (#9854)
Browse files Browse the repository at this point in the history
Adds support for two new actions:
* `globalSummon`, which can be used to activate a window using a _global_ (READ: OS-level) hotkey.
  - accepts an optional `name` argument. When provided, this will attempt to summon with the given name. When omitted, we'll try to summon the most recent window.
* `quakeMode` which is `globalSummon` for the `_quake` window.

These actions are stored in the actions array, but are read by the `WindowsTerminal` level and bound to the OS in `IslandWindow`. The monarch registers for these keybindings with the OS. When one is pressed, the monarch will recieve a `WM_HOTKEY` message. It'll use that to look up the corresponding action args. It'll use those to try and summon the right window.

## References

* #8888: Quake mode megathread
* #9274: Spec (**guys seriously i just need one more ✔️**)
* #9785: The start of granting "\_quake" super powers

## PR Checklist
* [x] Closes #653 - I'm gonna say this closes it for now, though we have _many_ follow-ups in #8888
* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed

* Validated that it works with `win` keys
* Validated that it works without `win` keys
* Validated that it hot-reloads
* Validated that it moves to the new monarch
* Validated that you can bind both `globalSummon` and `quakeMode` at the same time and do different things
* Validated that you can bind `globalSummon` with a name and it creates that name if it doesn't already exist
  • Loading branch information
zadjii-msft committed Apr 28, 2021
1 parent 1c414a7 commit d08271e
Show file tree
Hide file tree
Showing 35 changed files with 903 additions and 39 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/dictionary/apis.txt
Expand Up @@ -77,6 +77,7 @@ NOASYNC
NOCHANGEDIR
NOPROGRESS
NOREDIRECTIONBITMAP
NOREPEAT
ntprivapi
oaidl
ocidl
Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj
Expand Up @@ -25,6 +25,9 @@
<ClInclude Include="ProposeCommandlineResult.h">
<DependentUpon>Monarch.idl</DependentUpon>
</ClInclude>
<ClInclude Include="SummonWindowSelectionArgs.h">
<DependentUpon>Monarch.idl</DependentUpon>
</ClInclude>
<ClInclude Include="RenameRequestArgs.h">
<DependentUpon>Peasant.idl</DependentUpon>
</ClInclude>
Expand Down Expand Up @@ -54,6 +57,9 @@
<ClCompile Include="ProposeCommandlineResult.cpp">
<DependentUpon>Monarch.idl</DependentUpon>
</ClCompile>
<ClCompile Include="SummonWindowSelectionArgs.cpp">
<DependentUpon>Monarch.idl</DependentUpon>
</ClCompile>
<ClCompile Include="RenameRequestArgs.cpp">
<DependentUpon>Peasant.idl</DependentUpon>
</ClCompile>
Expand Down
49 changes: 48 additions & 1 deletion src/cascadia/Remoting/Monarch.cpp
Expand Up @@ -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
Expand Down Expand Up @@ -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:
// - <none>
// - 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));
}
}
}
1 change: 1 addition & 0 deletions src/cascadia/Remoting/Monarch.h
Expand Up @@ -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);

Expand Down
12 changes: 12 additions & 0 deletions src/cascadia/Remoting/Monarch.idl
Expand Up @@ -18,13 +18,25 @@ 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();

UInt64 GetPID();
UInt64 AddPeasant(IPeasant peasant);
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args);
void HandleActivatePeasant(WindowActivatedArgs args);
void SummonWindow(SummonWindowSelectionArgs args);

event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
};
Expand Down
19 changes: 19 additions & 0 deletions src/cascadia/Remoting/Peasant.cpp
Expand Up @@ -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:
// - <none>
// Return Value:
// - <none>
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,
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/Remoting/Peasant.h
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions src/cascadia/Remoting/Peasant.idl
Expand Up @@ -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<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonRequested;
};

[default_interface] runtimeclass Peasant : IPeasant
Expand Down
5 changes: 5 additions & 0 deletions 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"
40 changes: 40 additions & 0 deletions 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<SummonWindowSelectionArgs>
{
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);
}
16 changes: 16 additions & 0 deletions src/cascadia/Remoting/WindowManager.cpp
Expand Up @@ -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()
Expand Down Expand Up @@ -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);
}

}
3 changes: 3 additions & 0 deletions src/cascadia/Remoting/WindowManager.h
Expand Up @@ -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 };
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/Remoting/WindowManager.idl
Expand Up @@ -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<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
};
}
19 changes: 19 additions & 0 deletions src/cascadia/TerminalApp/AppActionHandlers.cpp
Expand Up @@ -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);
}
}
9 changes: 8 additions & 1 deletion src/cascadia/TerminalApp/AppLogic.cpp
Expand Up @@ -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.
Expand Down Expand Up @@ -1027,6 +1027,8 @@ namespace winrt::TerminalApp::implementation
_ApplyStartupTaskStateChange();

Jumplist::UpdateJumplist(_settings);

_SettingsChangedHandlers(*this, nullptr);
}

// Method Description:
Expand Down Expand Up @@ -1409,6 +1411,11 @@ namespace winrt::TerminalApp::implementation
return _root ? _root->AlwaysOnTop() : false;
}

Windows::Foundation::Collections::IMap<Microsoft::Terminal::Control::KeyChord, Microsoft::Terminal::Settings::Model::ActionAndArgs> AppLogic::GlobalHotkeys()
{
return _settings.GlobalSettings().KeyMap().GlobalHotkeys();
}

void AppLogic::IdentifyWindow()
{
if (_root)
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/AppLogic.h
Expand Up @@ -90,8 +90,11 @@ namespace winrt::TerminalApp::implementation

winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);

Windows::Foundation::Collections::IMap<Microsoft::Terminal::Control::KeyChord, Microsoft::Terminal::Settings::Model::ActionAndArgs> 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 };
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/AppLogic.idl
Expand Up @@ -71,6 +71,8 @@ namespace TerminalApp

FindTargetWindowResult FindTargetWindow(String[] args);

Windows.Foundation.Collections.IMap<Microsoft.Terminal.Control.KeyChord, Microsoft.Terminal.Settings.Model.ActionAndArgs> GlobalHotkeys();

// See IDialogPresenter and TerminalPage's DialogPresenter for more
// information.
Windows.Foundation.IAsyncOperation<Windows.UI.Xaml.Controls.ContentDialogResult> ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog);
Expand All @@ -86,6 +88,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> SettingsChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
}
}
3 changes: 1 addition & 2 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Expand Up @@ -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;
Expand All @@ -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() :
Expand Down

0 comments on commit d08271e

Please sign in to comment.