Add support for running a commandline in another WT window (#8898)
## Summary of the Pull Request **If you're reading this PR and haven't signed off on #8135, go there first.** ![window-management-000](https://user-images.githubusercontent.com/18356694/103932910-25199380-50e8-11eb-97e3-594a31da62d2.gif) This provides the basic parts of the implementation of #4472. Namely: * We add support for the `--window,-w <window-id>` argument to `wt.exe`, to allow a commandline to be given to another window. * If `window-id` is `0`, run the given commands in _the current window_. * If `window-id` is a negative number, run the commands in a _new_ Terminal window. * If `window-id` is the ID of an existing window, then run the commandline in that window. * If `window-id` is _not_ the ID of an existing window, create a new window. That window will be assigned the ID provided in the commandline. The provided subcommands will be run in that new window. * If `window-id` is omitted, then create a new window. ## References * Spec: #8135 * Megathread: #5000 * Project: projects/5 ## PR Checklist * [x] Closes #4472 * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - **sure does** ## Detailed Description of the Pull Request / Additional comments Note that `wt -w 1 -d c:\foo cmd.exe` does work, by causing window 1 to change There are limitations, and there are plenty of things to work on in the future: * [ ] We don't support names for windows yet * [ ] We don't support window glomming by default, or a setting to configure what happens when `-w` is omitted. I thought it best to lay the groundwork first, then come back to that. * [ ] `-w 0` currently just uses the "last activated" window, not "the current". There's more follow-up work to try and smartly find the actual window we're being called from. * [ ] Basically anything else that's listed in projects/5. I'm cutting this PR where it currently is, because this is already a huge PR. I believe the remaining tasks will all be easier to land, once this is in. ## Validation Steps Performed I've been creating windows, and closing them, and running cmdlines for a while now. I'm gonna keep doing that while the PR is open, till no bugs remain. # TODOs * [x] There are a bunch of `GetID`, `GetPID` calls that aren't try/caught 😬 - [x] `Monarch.cpp` - [x] `Peasant.cpp` - [x] `WindowManager.cpp` - [x] `AppHost.cpp` * [x] If the monarch gets hung, then _you can't launch any Terminals_ 😨 We should handle this gracefully. - Proposed idea: give the Monarch some time to respond to a proposal for a commandline. If there's no response in that timeframe, this window is now a _hermit_, outside of society entirely. It can't be elected Monarch. It can't receive command lines. It has no ID. - Could we gracefully recover from such a state? maybe, probably not though. - Same deal if a peasant hangs, it could end up hanging the monarch, right? Like if you do `wt -w 2`, and `2` is hung, then does the monarch get hung waiting on the hung peasant? - After talking with @miniksa, **we're gonna punt this from the initial implementation**. If people legit hit this in the wild, we'll fix it then.
This commit is contained in:
parent
8b2cdfd1f8
commit
03ebe514e9
|
@ -13,5 +13,6 @@ fixterms
|
|||
uk
|
||||
winui
|
||||
appshellintegration
|
||||
cppreference
|
||||
gfycat
|
||||
what3words
|
||||
|
|
|
@ -362,6 +362,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_Remoting", "src\c
|
|||
{27B5AAEB-A548-44CF-9777-F8BAA32AF7AE} = {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wpf", "wpf", "{4DAF0299-495E-4CD1-A982-9BAC16A45932}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|Any CPU = AuditMode|Any CPU
|
||||
|
@ -2475,7 +2477,7 @@ Global
|
|||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.Build.0 = AuditMode|x64
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.Build.0 = Release|x64
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
|
@ -2613,8 +2615,8 @@ Global
|
|||
{05500DEF-2294-41E3-AF9A-24E580B82836} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{1E4A062E-293B-4817-B20D-BF16B979E350} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{84848BFA-931D-42CE-9ADF-01EE54DE7890} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{376FE273-6B84-4EB5-8B30-8DE9D21B022C} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{84848BFA-931D-42CE-9ADF-01EE54DE7890} = {4DAF0299-495E-4CD1-A982-9BAC16A45932}
|
||||
{376FE273-6B84-4EB5-8B30-8DE9D21B022C} = {4DAF0299-495E-4CD1-A982-9BAC16A45932}
|
||||
{CA5CAD1A-9333-4D05-B12A-1905CBF112F9} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
|
@ -2634,7 +2636,7 @@ Global
|
|||
{024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87}
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {4DAF0299-495E-4CD1-A982-9BAC16A45932}
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
|
@ -2645,6 +2647,7 @@ Global
|
|||
{43CE4CE5-0010-4B99-9569-672670D26E26} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{27B5AAEB-A548-44CF-9777-F8BAA32AF7AE} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{68A10CD3-AA64-465B-AF5F-ED4E9700543C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{4DAF0299-495E-4CD1-A982-9BAC16A45932} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\wap-common.build.pre.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
|
||||
<!--
|
||||
|
|
|
@ -13,12 +13,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// It must be defined after CommandlineArgs.g.cpp, otherwise the compiler
|
||||
// will give you just the most impossible template errors to try and
|
||||
// decipher.
|
||||
void CommandlineArgs::Args(winrt::array_view<const winrt::hstring> const& value)
|
||||
void CommandlineArgs::Commandline(winrt::array_view<const winrt::hstring> const& value)
|
||||
{
|
||||
_args = { value.begin(), value.end() };
|
||||
}
|
||||
|
||||
winrt::com_array<winrt::hstring> CommandlineArgs::Args()
|
||||
winrt::com_array<winrt::hstring> CommandlineArgs::Commandline()
|
||||
{
|
||||
return winrt::com_array<winrt::hstring>{ _args.begin(), _args.end() };
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
|
||||
winrt::hstring CurrentDirectory() { return _cwd; };
|
||||
|
||||
void Args(winrt::array_view<const winrt::hstring> const& value);
|
||||
winrt::com_array<winrt::hstring> Args();
|
||||
void Commandline(winrt::array_view<const winrt::hstring> const& value);
|
||||
winrt::com_array<winrt::hstring> Commandline();
|
||||
|
||||
private:
|
||||
winrt::com_array<winrt::hstring> _args;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "FindTargetWindowArgs.h"
|
||||
#include "FindTargetWindowArgs.g.cpp"
|
|
@ -0,0 +1,35 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- FindTargetWindowArgs.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for determining which window a specific commandline is
|
||||
intended for. The Monarch will create one of these, then toss it over to
|
||||
TerminalApp. TerminalApp actually contains the logic for parsing a
|
||||
commandline, as well as settings like the windowing behavior. Once the
|
||||
TerminalApp determines the correct window, it'll fill in the
|
||||
ResultTargetWindow property. The monarch will then read that value out to
|
||||
invoke the commandline in the appropriate window.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FindTargetWindowArgs.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct FindTargetWindowArgs : public FindTargetWindowArgsT<FindTargetWindowArgs>
|
||||
{
|
||||
GETSET_PROPERTY(winrt::Microsoft::Terminal::Remoting::CommandlineArgs, Args, nullptr);
|
||||
GETSET_PROPERTY(int, ResultTargetWindow, -1);
|
||||
|
||||
public:
|
||||
FindTargetWindowArgs(winrt::Microsoft::Terminal::Remoting::CommandlineArgs args) :
|
||||
_Args{ args } {};
|
||||
};
|
||||
}
|
|
@ -19,6 +19,15 @@
|
|||
<ClInclude Include="Monarch.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FindTargetWindowArgs.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ProposeCommandlineResult.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowActivatedArgs.h">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="MonarchFactory.h" />
|
||||
<ClInclude Include="Peasant.h">
|
||||
|
@ -36,6 +45,15 @@
|
|||
<ClCompile Include="Monarch.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FindTargetWindowArgs.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProposeCommandlineResult.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowActivatedArgs.cpp">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
@ -49,6 +67,7 @@
|
|||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="init.cpp" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= idl Files ======================== -->
|
||||
<ItemGroup>
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "pch.h"
|
||||
#include "Monarch.h"
|
||||
#include "CommandlineArgs.h"
|
||||
#include "FindTargetWindowArgs.h"
|
||||
#include "ProposeCommandlineResult.h"
|
||||
|
||||
#include "Monarch.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
@ -44,33 +46,48 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// - the ID assigned to the peasant.
|
||||
uint64_t Monarch::AddPeasant(Remoting::IPeasant peasant)
|
||||
{
|
||||
// TODO:projects/5 This is terrible. There's gotta be a better way
|
||||
// of finding the first opening in a non-consecutive map of int->object
|
||||
const auto providedID = peasant.GetID();
|
||||
|
||||
if (providedID == 0)
|
||||
try
|
||||
{
|
||||
// Peasant doesn't currently have an ID. Assign it a new one.
|
||||
peasant.AssignID(_nextPeasantID++);
|
||||
// TODO:projects/5 This is terrible. There's gotta be a better way
|
||||
// of finding the first opening in a non-consecutive map of int->object
|
||||
const auto providedID = peasant.GetID();
|
||||
|
||||
if (providedID == 0)
|
||||
{
|
||||
// Peasant doesn't currently have an ID. Assign it a new one.
|
||||
peasant.AssignID(_nextPeasantID++);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Peasant already had an ID (from an older monarch). Leave that one
|
||||
// be. Make sure that the next peasant's ID is higher than it.
|
||||
_nextPeasantID = providedID >= _nextPeasantID ? providedID + 1 : _nextPeasantID;
|
||||
}
|
||||
|
||||
auto newPeasantsId = peasant.GetID();
|
||||
// Add an event listener to the peasant's WindowActivated event.
|
||||
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
|
||||
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_AddPeasant",
|
||||
TraceLoggingUInt64(providedID, "providedID", "the provided ID for the peasant"),
|
||||
TraceLoggingUInt64(newPeasantsId, "peasantID", "the ID of the new peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
return newPeasantsId;
|
||||
}
|
||||
else
|
||||
catch (...)
|
||||
{
|
||||
// Peasant already had an ID (from an older monarch). Leave that one
|
||||
// be. Make sure that the next peasant's ID is higher than it.
|
||||
_nextPeasantID = providedID >= _nextPeasantID ? providedID + 1 : _nextPeasantID;
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_AddPeasant_Failed",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// We can only get into this try/catch if the peasant died on us. So
|
||||
// the return value doesn't _really_ matter. They're not about to
|
||||
// get it.
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto newPeasantsId = peasant.GetID();
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
|
||||
// Add an event listener to the peasant's WindowActivated event.
|
||||
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
|
||||
|
||||
// TODO:projects/5 Wait on the peasant's PID, and remove them from the
|
||||
// map if they die. This won't work great in tests though, with fake
|
||||
// PIDs.
|
||||
|
||||
return newPeasantsId;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -79,19 +96,13 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// window".
|
||||
// Arguments:
|
||||
// - sender: the Peasant that raised this event. This might be out-of-proc!
|
||||
// - args: a bundle of the peasant ID, timestamp, and desktop ID, for the activated peasant
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Monarch::_peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
void Monarch::_peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const Remoting::WindowActivatedArgs& args)
|
||||
{
|
||||
// TODO:projects/5 Pass the desktop and timestamp of when the window was
|
||||
// activated in `args`.
|
||||
|
||||
if (auto peasant{ sender.try_as<Remoting::Peasant>() })
|
||||
{
|
||||
auto theirID = peasant.GetID();
|
||||
_setMostRecentPeasant(theirID);
|
||||
}
|
||||
HandleActivatePeasant(args);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -102,17 +113,81 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// - the peasant if it exists in our map, otherwise null
|
||||
Remoting::IPeasant Monarch::_getPeasant(uint64_t peasantID)
|
||||
{
|
||||
auto peasantSearch = _peasants.find(peasantID);
|
||||
return peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
try
|
||||
{
|
||||
const auto peasantSearch = _peasants.find(peasantID);
|
||||
auto maybeThePeasant = peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
// Ask the peasant for their PID. This will validate that they're
|
||||
// actually still alive.
|
||||
if (maybeThePeasant)
|
||||
{
|
||||
maybeThePeasant.GetPID();
|
||||
}
|
||||
return maybeThePeasant;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
// Remove the peasant from the list of peasants
|
||||
_peasants.erase(peasantID);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Monarch::_setMostRecentPeasant(const uint64_t peasantID)
|
||||
void Monarch::HandleActivatePeasant(const Remoting::WindowActivatedArgs& args)
|
||||
{
|
||||
// TODO:projects/5 Use a heap/priority queue per-desktop to track which
|
||||
// peasant was the most recent per-desktop. When we want to get the most
|
||||
// recent of all desktops (WindowingBehavior::UseExisting), then use the
|
||||
// most recent of all desktops.
|
||||
_mostRecentPeasant = peasantID;
|
||||
const auto oldLastActiveTime = _lastActivatedTime.time_since_epoch().count();
|
||||
const auto newLastActiveTime = args.ActivatedTime().time_since_epoch().count();
|
||||
|
||||
// For now, we'll just pay attention to whoever the most recent peasant
|
||||
// was. We're not too worried about the mru peasant dying. Worst case -
|
||||
// when the user executes a `wt -w 0`, we won't be able to find that
|
||||
// peasant, and it'll open in a new window instead of the current one.
|
||||
if (args.ActivatedTime() > _lastActivatedTime)
|
||||
{
|
||||
_mostRecentPeasant = args.PeasantID();
|
||||
_lastActivatedTime = args.ActivatedTime();
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SetMostRecentPeasant",
|
||||
TraceLoggingUInt64(args.PeasantID(), "peasantID", "the ID of the activated peasant"),
|
||||
TraceLoggingInt64(oldLastActiveTime, "oldLastActiveTime", "The previous lastActiveTime"),
|
||||
TraceLoggingInt64(newLastActiveTime, "newLastActiveTime", "The provided args.ActivatedTime()"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
uint64_t Monarch::_getMostRecentPeasantID()
|
||||
{
|
||||
if (_mostRecentPeasant != 0)
|
||||
{
|
||||
return _mostRecentPeasant;
|
||||
}
|
||||
|
||||
// We haven't yet been told the MRU peasant. Just use the first one.
|
||||
// This is just gonna be a random one, but really shouldn't happen
|
||||
// in practice. The WindowManager should set the MRU peasant
|
||||
// immediately as soon as it creates the monarch/peasant for the
|
||||
// first window.
|
||||
if (_peasants.size() > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _peasants.begin()->second.GetID();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// This shouldn't really happen. If we're the monarch, then the
|
||||
// first peasant should also _be us_. So we should be able to
|
||||
// get our own ID.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -122,16 +197,91 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
bool Monarch::ProposeCommandline(const Remoting::CommandlineArgs& /*args*/)
|
||||
// - true if the caller should create a new window for this commandline.
|
||||
// False otherwise - the monarch should have dispatched this commandline
|
||||
// to another window in this case.
|
||||
Remoting::ProposeCommandlineResult Monarch::ProposeCommandline(const Remoting::CommandlineArgs& args)
|
||||
{
|
||||
// TODO:projects/5
|
||||
// The branch dev/migrie/f/remote-commandlines has a more complete
|
||||
// version of this function, with a naive implementation. For now, we
|
||||
// always want to create a new window, so we'll just return true. This
|
||||
// will tell the caller that we didn't handle the commandline, and they
|
||||
// should open a new window to deal with it themselves.
|
||||
return true;
|
||||
// Raise an event, to ask how to handle this commandline. We can't ask
|
||||
// the app ourselves - we exist isolated from that knowledge (and
|
||||
// dependency hell). The WindowManager will raise this up to the app
|
||||
// host, which will then ask the AppLogic, who will then parse the
|
||||
// commandline and determine the provided ID of the window.
|
||||
auto findWindowArgs{ winrt::make_self<Remoting::implementation::FindTargetWindowArgs>(args) };
|
||||
|
||||
// This is handled by some handler in-proc
|
||||
_FindTargetWindowRequestedHandlers(*this, *findWindowArgs);
|
||||
|
||||
// After the event was handled, ResultTargetWindow() will be filled with
|
||||
// the parsed result.
|
||||
const auto targetWindow = findWindowArgs->ResultTargetWindow();
|
||||
|
||||
// If there's a valid ID returned, then let's try and find the peasant that goes with it.
|
||||
if (targetWindow >= 0)
|
||||
{
|
||||
uint64_t windowID = ::base::saturated_cast<uint64_t>(targetWindow);
|
||||
|
||||
if (windowID == 0)
|
||||
{
|
||||
windowID = _getMostRecentPeasantID();
|
||||
}
|
||||
|
||||
if (auto targetPeasant{ _getPeasant(windowID) })
|
||||
{
|
||||
auto result{ winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(false) };
|
||||
|
||||
try
|
||||
{
|
||||
// This will raise the peasant's ExecuteCommandlineRequested
|
||||
// event, which will then ask the AppHost to handle the
|
||||
// commandline, which will then pass it to AppLogic for
|
||||
// handling.
|
||||
targetPeasant.ExecuteCommandline(args);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to propose the commandline to the peasant (it
|
||||
// died?) then just tell this process to become a new window
|
||||
// instead.
|
||||
result->ShouldCreateWindow(true);
|
||||
|
||||
// If this fails, it'll be logged in the following
|
||||
// TraceLoggingWrite statement, with succeeded=false
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline_Existing",
|
||||
TraceLoggingUInt64(windowID, "peasantID", "the ID of the peasant the commandline waws intended for"),
|
||||
TraceLoggingBoolean(true, "foundMatch", "true if we found a peasant with that ID"),
|
||||
TraceLoggingBoolean(!result->ShouldCreateWindow(), "succeeded", "true if we successfully dispatched the commandline to the peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
return *result;
|
||||
}
|
||||
else if (windowID > 0)
|
||||
{
|
||||
// In this case, an ID was provided, but there's no
|
||||
// peasant with that ID. Instead, we should tell the caller that
|
||||
// they should make a new window, but _with that ID_.
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline_Existing",
|
||||
TraceLoggingUInt64(windowID, "peasantID", "the ID of the peasant the commandline waws intended for"),
|
||||
TraceLoggingBoolean(false, "foundMatch", "true if we found a peasant with that ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
auto result{ winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true) };
|
||||
result->Id(windowID);
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline_NewWindow",
|
||||
TraceLoggingInt64(targetWindow, "targetWindow", "The provided ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// In this case, no usable ID was provided. Return { true, nullopt }
|
||||
return winrt::make<Remoting::implementation::ProposeCommandlineResult>(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,7 +52,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
|
||||
uint64_t AddPeasant(winrt::Microsoft::Terminal::Remoting::IPeasant peasant);
|
||||
|
||||
bool ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
winrt::Microsoft::Terminal::Remoting::ProposeCommandlineResult ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
|
||||
private:
|
||||
Monarch(const uint64_t testPID);
|
||||
|
@ -61,14 +64,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
uint64_t _nextPeasantID{ 1 };
|
||||
uint64_t _thisPeasantID{ 0 };
|
||||
uint64_t _mostRecentPeasant{ 0 };
|
||||
winrt::Windows::Foundation::DateTime _lastActivatedTime{};
|
||||
|
||||
WindowingBehavior _windowingBehavior{ WindowingBehavior::UseNew };
|
||||
std::unordered_map<uint64_t, winrt::Microsoft::Terminal::Remoting::IPeasant> _peasants;
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _getPeasant(uint64_t peasantID);
|
||||
void _setMostRecentPeasant(const uint64_t peasantID);
|
||||
uint64_t _getMostRecentPeasantID();
|
||||
|
||||
void _peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
|
||||
friend class RemotingUnitTests::RemotingTests;
|
||||
};
|
||||
|
|
|
@ -5,11 +5,26 @@ import "Peasant.idl";
|
|||
|
||||
namespace Microsoft.Terminal.Remoting
|
||||
{
|
||||
|
||||
[default_interface] runtimeclass FindTargetWindowArgs {
|
||||
CommandlineArgs Args { get; };
|
||||
Int32 ResultTargetWindow;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ProposeCommandlineResult {
|
||||
Windows.Foundation.IReference<UInt64> Id { get; };
|
||||
// TODO:projects/5 - also return the name here, if the name was set on the commandline
|
||||
Boolean ShouldCreateWindow { get; }; // If you name this `CreateWindow`, the compiler will explode
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass Monarch {
|
||||
Monarch();
|
||||
|
||||
UInt64 GetPID();
|
||||
UInt64 AddPeasant(IPeasant peasant);
|
||||
Boolean ProposeCommandline(CommandlineArgs args);
|
||||
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args);
|
||||
void HandleActivatePeasant(WindowActivatedArgs args);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -50,6 +50,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
_initialArgs = args;
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_ExecuteCommandline",
|
||||
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
|
||||
TraceLoggingWideString(args.CurrentDirectory().c_str(), "directory", "the provided cwd"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// Raise an event with these args. The AppHost will listen for this
|
||||
// event to know when to take these args and dispatch them to a
|
||||
// currently-running window.
|
||||
|
@ -63,4 +69,52 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
return _initialArgs;
|
||||
}
|
||||
|
||||
void Peasant::ActivateWindow(const Remoting::WindowActivatedArgs& args)
|
||||
{
|
||||
// TODO: projects/5 - somehow, pass an identifier for the current
|
||||
// desktop into this method. The Peasant shouldn't need to be able to
|
||||
// figure it out, but it will need to report it to the monarch.
|
||||
|
||||
// Store these new args as our last activated state. If a new monarch
|
||||
// comes looking, we can use this info to tell them when we were last
|
||||
// activated.
|
||||
_lastActivatedArgs = args;
|
||||
|
||||
bool successfullyNotified = false;
|
||||
// Raise our WindowActivated event, to let the monarch know we've been
|
||||
// activated.
|
||||
try
|
||||
{
|
||||
// Try/catch this, because the other side of this event is handled
|
||||
// by the monarch. The monarch might have died. If they have, this
|
||||
// will throw an exception. Just eat it, the election thread will
|
||||
// handle hooking up the new one.
|
||||
_WindowActivatedHandlers(*this, args);
|
||||
successfullyNotified = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_ActivateWindow",
|
||||
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
|
||||
TraceLoggingBoolean(successfullyNotified, "successfullyNotified", "true if we successfully notified the monarch"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieve the WindowActivatedArgs describing the last activation of this
|
||||
// peasant. New monarchs can use this state to determine when we were last
|
||||
// activated.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a WindowActivatedArgs with info about when and where we were last activated.
|
||||
Remoting::WindowActivatedArgs Peasant::GetLastActivatedArgs()
|
||||
{
|
||||
return _lastActivatedArgs;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,9 +21,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
uint64_t GetPID();
|
||||
|
||||
bool ExecuteCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
void ActivateWindow(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs);
|
||||
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::CommandlineArgs);
|
||||
|
||||
private:
|
||||
|
@ -33,6 +36,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
uint64_t _id{ 0 };
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs _initialArgs{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs _lastActivatedArgs{ nullptr };
|
||||
|
||||
friend class RemotingUnitTests::RemotingTests;
|
||||
};
|
||||
|
|
|
@ -9,10 +9,18 @@ namespace Microsoft.Terminal.Remoting
|
|||
CommandlineArgs();
|
||||
CommandlineArgs(String[] args, String cwd);
|
||||
|
||||
String[] Args { get; set; };
|
||||
String[] Commandline { get; set; };
|
||||
String CurrentDirectory();
|
||||
};
|
||||
|
||||
runtimeclass WindowActivatedArgs
|
||||
{
|
||||
WindowActivatedArgs(UInt64 peasantID, Guid desktopID, Windows.Foundation.DateTime activatedTime);
|
||||
UInt64 PeasantID { get; };
|
||||
Guid DesktopID { get; };
|
||||
Windows.Foundation.DateTime ActivatedTime { get; };
|
||||
};
|
||||
|
||||
interface IPeasant
|
||||
{
|
||||
CommandlineArgs InitialArgs { get; };
|
||||
|
@ -21,7 +29,10 @@ namespace Microsoft.Terminal.Remoting
|
|||
UInt64 GetID();
|
||||
UInt64 GetPID();
|
||||
Boolean ExecuteCommandline(CommandlineArgs args);
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowActivated;
|
||||
void ActivateWindow(WindowActivatedArgs args);
|
||||
WindowActivatedArgs GetLastActivatedArgs();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
|
||||
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "ProposeCommandlineResult.h"
|
||||
#include "ProposeCommandlineResult.g.cpp"
|
|
@ -0,0 +1,36 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- ProposeCommandlineResult.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for encapsulating the result of a
|
||||
Monarch::ProposeCommandline call. The monarch will be telling the new process
|
||||
whether it should create a new window or not. If the value of
|
||||
ShouldCreateWindow is false, that implies that some other window process was
|
||||
given the commandline for handling, and the caller should just exit.
|
||||
- If ShouldCreateWindow is true, the Id property may or may not contain an ID
|
||||
that the new window should use as it's ID.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ProposeCommandlineResult.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct ProposeCommandlineResult : public ProposeCommandlineResultT<ProposeCommandlineResult>
|
||||
{
|
||||
public:
|
||||
GETSET_PROPERTY(Windows::Foundation::IReference<uint64_t>, Id);
|
||||
GETSET_PROPERTY(bool, ShouldCreateWindow, true);
|
||||
|
||||
public:
|
||||
ProposeCommandlineResult(bool shouldCreateWindow) :
|
||||
_ShouldCreateWindow{ shouldCreateWindow } {};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "WindowActivatedArgs.h"
|
||||
#include "WindowActivatedArgs.g.cpp"
|
|
@ -0,0 +1,38 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- WindowActivatedArgs.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for encapsulating all the information about when and
|
||||
where a window was activated. This will be used by the Monarch to determine
|
||||
who the most recent peasant is.
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "WindowActivatedArgs.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct WindowActivatedArgs : public WindowActivatedArgsT<WindowActivatedArgs>
|
||||
{
|
||||
GETSET_PROPERTY(uint64_t, PeasantID, 0);
|
||||
GETSET_PROPERTY(winrt::guid, DesktopID, {});
|
||||
GETSET_PROPERTY(winrt::Windows::Foundation::DateTime, ActivatedTime, {});
|
||||
|
||||
public:
|
||||
WindowActivatedArgs(uint64_t peasantID, winrt::guid desktopID, winrt::Windows::Foundation::DateTime timestamp) :
|
||||
_PeasantID{ peasantID },
|
||||
_DesktopID{ desktopID },
|
||||
_ActivatedTime{ timestamp } {};
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(WindowActivatedArgs);
|
||||
}
|
|
@ -18,10 +18,29 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
{
|
||||
WindowManager::WindowManager()
|
||||
{
|
||||
_monarchWaitInterrupt.create();
|
||||
|
||||
// Register with COM as a server for the Monarch class
|
||||
_registerAsMonarch();
|
||||
// Instantiate an instance of the Monarch. This may or may not be in-proc!
|
||||
_createMonarch();
|
||||
bool foundMonarch = false;
|
||||
while (!foundMonarch)
|
||||
{
|
||||
try
|
||||
{
|
||||
_createMonarchAndCallbacks();
|
||||
// _createMonarchAndCallbacks will initialize _isKing
|
||||
foundMonarch = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to find the monarch,
|
||||
// stay in this jail until we do.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInCtor",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WindowManager::~WindowManager()
|
||||
|
@ -32,23 +51,82 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// monarch!
|
||||
CoRevokeClassObject(_registrationHostClass);
|
||||
_registrationHostClass = 0;
|
||||
_monarchWaitInterrupt.SetEvent();
|
||||
|
||||
// A thread is joinable once it's been started. Basically this just
|
||||
// makes sure that the thread isn't just default-constructed.
|
||||
if (_electionThread.joinable())
|
||||
{
|
||||
_electionThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args)
|
||||
{
|
||||
const bool isKing = _areWeTheKing();
|
||||
// If we're the king, we _definitely_ want to process the arguments, we were
|
||||
// launched with them!
|
||||
//
|
||||
// Otherwise, the King will tell us if we should make a new window
|
||||
_shouldCreateWindow = isKing ||
|
||||
_monarch.ProposeCommandline(args);
|
||||
_shouldCreateWindow = _isKing;
|
||||
std::optional<uint64_t> givenID;
|
||||
if (!_isKing)
|
||||
{
|
||||
// The monarch may respond back "you should be a new
|
||||
// window, with ID,name of (id, name)". Really the responses are:
|
||||
// * You should not create a new window
|
||||
// * Create a new window (but without a given ID or name). The
|
||||
// Monarch will assign your ID/name later
|
||||
// * Create a new window, and you'll have this ID or name
|
||||
// - This is the case where the user provides `wt -w 1`, and
|
||||
// there's no existing window 1
|
||||
|
||||
const auto result = _monarch.ProposeCommandline(args);
|
||||
_shouldCreateWindow = result.ShouldCreateWindow();
|
||||
if (result.Id())
|
||||
{
|
||||
givenID = result.Id().Value();
|
||||
}
|
||||
|
||||
// TraceLogging doesn't have a good solution for logging an
|
||||
// optional. So we have to repeat the calls here:
|
||||
if (givenID)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(givenID.value(), "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingPointer(nullptr, "Id", "No ID provided"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're the monarch, we don't need to propose anything. We're just
|
||||
// going to do it.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline_AsMonarch",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
if (_shouldCreateWindow)
|
||||
{
|
||||
// If we should create a new window, then instantiate our Peasant
|
||||
// instance, and tell that peasant to handle that commandline.
|
||||
_createOurPeasant();
|
||||
_createOurPeasant({ givenID });
|
||||
|
||||
// Spawn a thread to wait on the monarch, and handle the election
|
||||
if (!_isKing)
|
||||
{
|
||||
_createPeasantThread();
|
||||
}
|
||||
|
||||
_peasant.ExecuteCommandline(args);
|
||||
}
|
||||
|
@ -83,27 +161,269 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
CLSCTX_LOCAL_SERVER);
|
||||
}
|
||||
|
||||
// NOTE: This can throw! Callers include:
|
||||
// - the constructor, who performs this in a loop until it successfully
|
||||
// find a a monarch
|
||||
// - the performElection method, which is called in the waitOnMonarch
|
||||
// thread. All the calls in that thread are wrapped in try/catch's
|
||||
// already.
|
||||
// - _createOurPeasant, who might do this in a loop to establish us with the
|
||||
// monarch.
|
||||
void WindowManager::_createMonarchAndCallbacks()
|
||||
{
|
||||
_createMonarch();
|
||||
// Save the result of checking if we're the king. We want to avoid
|
||||
// unnecessary calls back and forth if we can.
|
||||
_isKing = _areWeTheKing();
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ConnectedToMonarch",
|
||||
TraceLoggingUInt64(_monarch.GetPID(), "monarchPID", "The PID of the new Monarch"),
|
||||
TraceLoggingBoolean(_isKing, "isKing", "true if we are the new monarch"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
if (_peasant)
|
||||
{
|
||||
// Inform the monarch of the time we were last activated
|
||||
_monarch.HandleActivatePeasant(_peasant.GetLastActivatedArgs());
|
||||
}
|
||||
|
||||
if (!_isKing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Here, we're the king!
|
||||
//
|
||||
// This is where you should do any additional setup that might need to be
|
||||
// done when we become the king. THis will be called both for the first
|
||||
// window, and when the current monarch dies.
|
||||
|
||||
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
|
||||
}
|
||||
|
||||
bool WindowManager::_areWeTheKing()
|
||||
{
|
||||
const auto kingPID{ _monarch.GetPID() };
|
||||
const auto ourPID{ GetCurrentProcessId() };
|
||||
const auto kingPID{ _monarch.GetPID() };
|
||||
return (ourPID == kingPID);
|
||||
}
|
||||
|
||||
Remoting::IPeasant WindowManager::_createOurPeasant()
|
||||
Remoting::IPeasant WindowManager::_createOurPeasant(std::optional<uint64_t> givenID)
|
||||
{
|
||||
auto p = winrt::make_self<Remoting::implementation::Peasant>();
|
||||
if (givenID)
|
||||
{
|
||||
p->AssignID(givenID.value());
|
||||
}
|
||||
_peasant = *p;
|
||||
_monarch.AddPeasant(_peasant);
|
||||
|
||||
// TODO:projects/5 Spawn a thread to wait on the monarch, and handle the election
|
||||
// Try to add us to the monarch. If that fails, try to find a monarch
|
||||
// again, until we find one (we will eventually find us)
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
_monarch.AddPeasant(_peasant);
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Wrap this in it's own try/catch, because this can throw.
|
||||
_createMonarchAndCallbacks();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_CreateOurPeasant",
|
||||
TraceLoggingUInt64(_peasant.GetID(), "peasantID", "The ID of our new peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
return _peasant;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempt to connect to the monarch process. This might be us!
|
||||
// - For the new monarch, add us to their list of peasants.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff we're the new monarch process.
|
||||
// NOTE: This can throw!
|
||||
bool WindowManager::_performElection()
|
||||
{
|
||||
_createMonarchAndCallbacks();
|
||||
|
||||
// Tell the new monarch who we are. We might be that monarch!
|
||||
_monarch.AddPeasant(_peasant);
|
||||
|
||||
// This method is only called when a _new_ monarch is elected. So
|
||||
// don't do anything here that needs to be done for all monarch
|
||||
// windows. This should only be for work that's done when a window
|
||||
// _becomes_ a monarch, after the death of the previous monarch.
|
||||
return _isKing;
|
||||
}
|
||||
|
||||
void WindowManager::_createPeasantThread()
|
||||
{
|
||||
// If we catch an exception trying to get at the monarch ever, we can
|
||||
// set the _monarchWaitInterrupt, and use that to trigger a new
|
||||
// election. Though, we wouldn't be able to retry the function that
|
||||
// caused the exception in the first place...
|
||||
|
||||
_electionThread = std::thread([this] {
|
||||
_waitOnMonarchThread();
|
||||
});
|
||||
}
|
||||
|
||||
void WindowManager::_waitOnMonarchThread()
|
||||
{
|
||||
// This is the array of HANDLEs that we're going to wait on in
|
||||
// WaitForMultipleObjects below.
|
||||
// * waits[0] will be the handle to the monarch process. It gets
|
||||
// signalled when the process exits / dies.
|
||||
// * waits[1] is the handle to our _monarchWaitInterrupt event. Another
|
||||
// thread can use that to manually break this loop. We'll do that when
|
||||
// we're getting torn down.
|
||||
HANDLE waits[2];
|
||||
waits[1] = _monarchWaitInterrupt.get();
|
||||
const auto peasantID = _peasant.GetID(); // safe: _peasant is in-proc.
|
||||
|
||||
bool exitThreadRequested = false;
|
||||
while (!exitThreadRequested)
|
||||
{
|
||||
// At any point in all this, the current monarch might die. If it
|
||||
// does, we'll go straight to a new election, in the "jail"
|
||||
// try/catch below. Worst case, eventually, we'll become the new
|
||||
// monarch.
|
||||
try
|
||||
{
|
||||
// This might fail to even ask the monarch for it's PID.
|
||||
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS,
|
||||
FALSE,
|
||||
static_cast<DWORD>(_monarch.GetPID())) };
|
||||
|
||||
// If we fail to open the monarch, then they don't exist
|
||||
// anymore! Go straight to an election.
|
||||
if (hMonarch.get() == nullptr)
|
||||
{
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_FailedToOpenMonarch",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
exitThreadRequested = _performElection();
|
||||
continue;
|
||||
}
|
||||
|
||||
waits[0] = hMonarch.get();
|
||||
auto waitResult = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
|
||||
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0 + 0: // waits[0] was signaled, the handle to the monarch process
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchDied",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
// Connect to the new monarch, which might be us!
|
||||
// If we become the monarch, then we'll return true and exit this thread.
|
||||
exitThreadRequested = _performElection();
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 1: // waits[1] was signaled, our manual interrupt
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchWaitInterrupted",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
exitThreadRequested = true;
|
||||
break;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
// This should be impossible.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchWaitTimeout",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
exitThreadRequested = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
// Returning any other value is invalid. Just die.
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_WaitFailed",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Theoretically, if window[1] dies when we're trying to get
|
||||
// it's PID we'll get here. If we just try to do the election
|
||||
// once here, it's possible we might elect window[2], but have
|
||||
// it die before we add ourselves as a peasant. That
|
||||
// _performElection call will throw, and we wouldn't catch it
|
||||
// here, and we'd die.
|
||||
|
||||
// Instead, we're going to have a resilient election process.
|
||||
// We're going to keep trying an election, until one _doesn't_
|
||||
// throw an exception. That might mean burning through all the
|
||||
// other dying monarchs until we find us as the monarch. But if
|
||||
// this process is alive, then there's _someone_ in the line of
|
||||
// succession.
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInWaitThread",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
bool foundNewMonarch = false;
|
||||
while (!foundNewMonarch)
|
||||
{
|
||||
try
|
||||
{
|
||||
exitThreadRequested = _performElection();
|
||||
// It doesn't matter if we're the monarch, or someone
|
||||
// else is, but if we complete the election, then we've
|
||||
// registered with a new one. We can escape this jail
|
||||
// and re-enter society.
|
||||
foundNewMonarch = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to acknowledge the results of the election,
|
||||
// stay in this jail until we do.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInNestedWaitThread",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Remoting::Peasant WindowManager::CurrentWindow()
|
||||
{
|
||||
return _peasant;
|
||||
}
|
||||
|
||||
void WindowManager::_raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args)
|
||||
{
|
||||
_FindTargetWindowRequestedHandlers(sender, args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- WindowManager.h
|
||||
|
||||
Abstract:
|
||||
- The Window Manager takes care of coordinating the monarch and peasant for this
|
||||
process.
|
||||
- It's responsible for registering as a potential future monarch. It's also
|
||||
responsible for creating the Peasant for this process when it's determined
|
||||
this process should become a window process.
|
||||
- If we aren't the monarch, it's responsible for watching the current monarch
|
||||
process, and finding the new one if the current monarch dies.
|
||||
- When the monarch needs to ask the TerminalApp about how to parse a
|
||||
commandline, it'll ask by raising an event that we'll bubble up to the
|
||||
AppHost.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
@ -20,16 +38,29 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant CurrentWindow();
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
|
||||
private:
|
||||
bool _shouldCreateWindow{ false };
|
||||
bool _isKing{ false };
|
||||
DWORD _registrationHostClass{ 0 };
|
||||
winrt::Microsoft::Terminal::Remoting::Monarch _monarch{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
|
||||
|
||||
wil::unique_event _monarchWaitInterrupt;
|
||||
std::thread _electionThread;
|
||||
|
||||
void _registerAsMonarch();
|
||||
void _createMonarch();
|
||||
void _createMonarchAndCallbacks();
|
||||
bool _areWeTheKing();
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant();
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant(std::optional<uint64_t> givenID);
|
||||
|
||||
bool _performElection();
|
||||
void _createPeasantThread();
|
||||
void _waitOnMonarchThread();
|
||||
void _raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import "Peasant.idl";
|
||||
import "Monarch.idl";
|
||||
|
||||
|
||||
namespace Microsoft.Terminal.Remoting
|
||||
|
@ -9,5 +10,6 @@ namespace Microsoft.Terminal.Remoting
|
|||
void ProposeCommandline(CommandlineArgs args);
|
||||
Boolean ShouldCreateWindow { get; };
|
||||
IPeasant CurrentWindow();
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Corporation
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include <LibraryResources.h>
|
||||
#include <WilErrorReporting.h>
|
||||
|
||||
// Note: Generate GUID using TlgGuid.exe tool
|
||||
#pragma warning(suppress : 26477) // One of the macros uses 0/NULL. We don't have control to make it nullptr.
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hRemotingProvider,
|
||||
"Microsoft.Windows.Terminal.Remoting",
|
||||
// {d6f04aad-629f-539a-77c1-73f5c3e4aa7b}
|
||||
(0xd6f04aad, 0x629f, 0x539a, 0x77, 0xc1, 0x73, 0xf5, 0xc3, 0xe4, 0xaa, 0x7b),
|
||||
TraceLoggingOptionMicrosoftTelemetry());
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
|
||||
{
|
||||
switch (reason)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
DisableThreadLibraryCalls(hInstDll);
|
||||
TraceLoggingRegister(g_hRemotingProvider);
|
||||
Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hRemotingProvider);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
if (g_hRemotingProvider)
|
||||
{
|
||||
TraceLoggingUnregister(g_hRemotingProvider);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"Microsoft.Terminal.Remoting/Resources");
|
|
@ -38,7 +38,7 @@
|
|||
// Including TraceLogging essentials for the binary
|
||||
#include <TraceLoggingProvider.h>
|
||||
#include <winmeta.h>
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hRemotingProvider);
|
||||
#include <telemetry/ProjectTelemetry.h>
|
||||
#include <TraceLoggingActivity.h>
|
||||
|
||||
|
|
|
@ -185,6 +185,10 @@ void AppCommandlineArgs::_buildParser()
|
|||
maximized->excludes(fullscreen);
|
||||
focus->excludes(fullscreen);
|
||||
|
||||
_app.add_option("-w,--window",
|
||||
_windowTarget,
|
||||
RS_A(L"CmdWindowTargetArgDesc"));
|
||||
|
||||
// Subcommands
|
||||
_buildNewTabParser();
|
||||
_buildSplitPaneParser();
|
||||
|
@ -531,6 +535,7 @@ void AppCommandlineArgs::_resetStateToDefault()
|
|||
// DON'T clear _launchMode here! This will get called once for every
|
||||
// subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out
|
||||
// the "global" fullscreen flag (-F).
|
||||
// Same with _windowTarget.
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
|
@ -848,4 +853,11 @@ void AppCommandlineArgs::FullResetState()
|
|||
_startupActions.clear();
|
||||
_exitMessage = "";
|
||||
_shouldExitEarly = false;
|
||||
|
||||
_windowTarget = -1;
|
||||
}
|
||||
|
||||
int AppCommandlineArgs::GetTargetWindow() const noexcept
|
||||
{
|
||||
return _windowTarget;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ public:
|
|||
void DisableHelpInExitMessage();
|
||||
void FullResetState();
|
||||
|
||||
int GetTargetWindow() const noexcept;
|
||||
|
||||
private:
|
||||
static const std::wregex _commandDelimiterRegex;
|
||||
|
||||
|
@ -103,6 +105,8 @@ private:
|
|||
std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions;
|
||||
std::string _exitMessage;
|
||||
bool _shouldExitEarly{ false };
|
||||
|
||||
int _windowTarget{ -1 };
|
||||
// Are you adding more args or attributes here? If they are not reset in _resetStateToDefault, make sure to reset them in FullResetState
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand);
|
||||
|
|
|
@ -1151,17 +1151,84 @@ namespace winrt::TerminalApp::implementation
|
|||
return result;
|
||||
}
|
||||
|
||||
int32_t AppLogic::ExecuteCommandline(array_view<const winrt::hstring> args)
|
||||
// Method Description:
|
||||
// - Parse the provided commandline arguments into actions, and try to
|
||||
// perform them immediately.
|
||||
// - This function returns 0, unless a there was a non-zero result from
|
||||
// trying to parse one of the commands provided. In that case, no commands
|
||||
// after the failing command will be parsed, and the non-zero code
|
||||
// returned.
|
||||
// - If a non-empty cwd is provided, the entire terminal exe will switch to
|
||||
// that CWD while we handle these actions, then return to the original
|
||||
// CWD.
|
||||
// Arguments:
|
||||
// - args: an array of strings to process as a commandline. These args can contain spaces
|
||||
// - cwd: The directory to use as the CWD while performing these actions.
|
||||
// Return Value:
|
||||
// - the result of the first command who's parsing returned a non-zero code,
|
||||
// or 0. (see AppLogic::_ParseArgs)
|
||||
int32_t AppLogic::ExecuteCommandline(array_view<const winrt::hstring> args,
|
||||
const winrt::hstring& cwd)
|
||||
{
|
||||
::TerminalApp::AppCommandlineArgs appArgs;
|
||||
auto result = appArgs.ParseArgs(args);
|
||||
if (result == 0)
|
||||
{
|
||||
auto actions = winrt::single_threaded_vector<ActionAndArgs>(std::move(appArgs.GetStartupActions()));
|
||||
_root->ProcessStartupActions(actions, false);
|
||||
|
||||
_root->ProcessStartupActions(actions, false, cwd);
|
||||
}
|
||||
// Return the result of parsing with commandline, though it may or may not be used.
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Parse the given commandline args in an attempt to find the specified
|
||||
// window. The rest of the args are ignored for now (they'll be handled
|
||||
// whenever the commandline gets to the window it was intended for).
|
||||
// - Note that this function will only ever be called by the monarch. A
|
||||
// return value of `0` in this case does not mean "run the commandline in
|
||||
// _this_ process", rather it means "run the commandline in the current
|
||||
// process", whoever that may be.
|
||||
// Arguments:
|
||||
// - args: an array of strings to process as a commandline. These args can contain spaces
|
||||
// Return Value:
|
||||
// - 0: We should handle the args "in the current window".
|
||||
// - -1: We should handle the args in a new window
|
||||
// - anything else: We should handle the commandline in the window with the given ID.
|
||||
int32_t AppLogic::FindTargetWindow(array_view<const winrt::hstring> args)
|
||||
{
|
||||
::TerminalApp::AppCommandlineArgs appArgs;
|
||||
const auto result = appArgs.ParseArgs(args);
|
||||
if (result == 0)
|
||||
{
|
||||
return appArgs.GetTargetWindow();
|
||||
|
||||
// TODO:projects/5
|
||||
//
|
||||
// In the future, we'll want to use the windowingBehavior setting to
|
||||
// determine what happens when a window ID wasn't manually provided.
|
||||
//
|
||||
// Maybe that'd be a special return value out of here, to tell the
|
||||
// monarch to do something special:
|
||||
//
|
||||
// -1 -> create a new window
|
||||
// -2 -> find the mru, this desktop
|
||||
// -3 -> MRU, any desktop (is this not just 0?)
|
||||
}
|
||||
|
||||
return result; // TODO:MG does a return value make sense
|
||||
// Any unsuccessful parse will be a new window. That new window will try
|
||||
// to handle the commandline itself, and find that the commandline
|
||||
// failed to parse. When that happens, the new window will display the
|
||||
// message box.
|
||||
//
|
||||
// This will also work for the case where the user specifies an invalid
|
||||
// commandline in conjunction with `-w 0`. This function will determine
|
||||
// that the commandline has a parse error, and indicate that we should
|
||||
// create a new window. Then, in that new window, we'll try to set the
|
||||
// StartupActions, which will again fail, returning the correct error
|
||||
// message.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
|
|
@ -29,7 +29,8 @@ namespace winrt::TerminalApp::implementation
|
|||
[[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept;
|
||||
|
||||
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
|
||||
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions);
|
||||
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions, const winrt::hstring& cwd);
|
||||
int32_t FindTargetWindow(array_view<const winrt::hstring> actions);
|
||||
winrt::hstring ParseCommandlineMessage();
|
||||
bool ShouldExitEarly();
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace TerminalApp
|
|||
Boolean IsElevated();
|
||||
|
||||
Int32 SetStartupCommandline(String[] commands);
|
||||
Int32 ExecuteCommandline(String[] commands);
|
||||
Int32 ExecuteCommandline(String[] commands, String cwd);
|
||||
String ParseCommandlineMessage { get; };
|
||||
Boolean ShouldExitEarly { get; };
|
||||
|
||||
|
@ -56,6 +56,8 @@ namespace TerminalApp
|
|||
UInt64 GetLastActiveControlTaskbarState();
|
||||
UInt64 GetLastActiveControlTaskbarProgress();
|
||||
|
||||
Int32 FindTargetWindow(String[] args);
|
||||
|
||||
// See IDialogPresenter and TerminalPage's DialogPresenter for more
|
||||
// information.
|
||||
Windows.Foundation.IAsyncOperation<Windows.UI.Xaml.Controls.ContentDialogResult> ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog);
|
||||
|
|
|
@ -335,6 +335,9 @@
|
|||
<data name="CmdFocusDesc" xml:space="preserve">
|
||||
<value>Launch the window in focus mode</value>
|
||||
</data>
|
||||
<data name="CmdWindowTargetArgDesc" xml:space="preserve">
|
||||
<value>Specify a terminal window to run the given commandline in. "0" always refers to the current window. </value>
|
||||
</data>
|
||||
<data name="NewTabSplitButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.HelpText" xml:space="preserve">
|
||||
<value>Press the button to open a new terminal tab with your default profile. Open the flyout to select which profile you want to open.</value>
|
||||
</data>
|
||||
|
|
|
@ -354,10 +354,14 @@ namespace winrt::TerminalApp::implementation
|
|||
// other side of the co_await.
|
||||
// - initial: if true, we're parsing these args during startup, and we
|
||||
// should fire an Initialized event.
|
||||
// - cwd: If not empty, we should try switching to this provided directory
|
||||
// while processing these actions. This will allow something like `wt -w 0
|
||||
// nt -d .` from inside another directory to work as expected.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget TerminalPage::ProcessStartupActions(Windows::Foundation::Collections::IVector<ActionAndArgs> actions,
|
||||
const bool initial)
|
||||
const bool initial,
|
||||
const winrt::hstring cwd)
|
||||
{
|
||||
// If there are no actions left, do nothing.
|
||||
if (actions.Size() == 0)
|
||||
|
@ -368,6 +372,30 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
// Handle it on a subsequent pass of the UI thread.
|
||||
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
|
||||
|
||||
// If the caller provided a CWD, switch to that directory, then switch
|
||||
// back once we're done. This looks weird though, because we have to set
|
||||
// up the scope_exit _first_. We'll release the scope_exit if we don't
|
||||
// actually need it.
|
||||
std::wstring originalCwd{ wil::GetCurrentDirectoryW<std::wstring>() };
|
||||
auto restoreCwd = wil::scope_exit([&originalCwd]() {
|
||||
// ignore errors, we'll just power on through. We'd rather do
|
||||
// something rather than fail silently if the directory doesn't
|
||||
// actually exist.
|
||||
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectory(originalCwd.c_str()));
|
||||
});
|
||||
if (cwd.empty())
|
||||
{
|
||||
restoreCwd.release();
|
||||
}
|
||||
else
|
||||
{
|
||||
// ignore errors, we'll just power on through. We'd rather do
|
||||
// something rather than fail silently if the directory doesn't
|
||||
// actually exist.
|
||||
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectory(cwd.c_str()));
|
||||
}
|
||||
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
for (const auto& action : actions)
|
||||
|
@ -883,9 +911,28 @@ namespace winrt::TerminalApp::implementation
|
|||
envMap.Insert(L"WT_PROFILE_ID", guidWString);
|
||||
envMap.Insert(L"WSLENV", L"WT_PROFILE_ID");
|
||||
|
||||
// Update the path to be relative to whatever our CWD is.
|
||||
//
|
||||
// Refer to the examples in
|
||||
// https://en.cppreference.com/w/cpp/filesystem/path/append
|
||||
//
|
||||
// We need to do this here, to ensure we tell the ConptyConnection
|
||||
// the correct starting path. If we're being invoked from another
|
||||
// terminal instance (e.g. wt -w 0 -d .), then we have switched our
|
||||
// CWD to the provided path. We should treat the StartingDirectory
|
||||
// as relative to the current CWD.
|
||||
//
|
||||
// The connection must be informed of the current CWD on
|
||||
// construction, because the connection might not spawn the child
|
||||
// process until later, on another thread, after we've already
|
||||
// restored the CWD to it's original value.
|
||||
std::wstring cwdString{ wil::GetCurrentDirectoryW<std::wstring>() };
|
||||
std::filesystem::path cwd{ cwdString };
|
||||
cwd /= settings.StartingDirectory().c_str();
|
||||
|
||||
auto conhostConn = TerminalConnection::ConptyConnection(
|
||||
settings.Commandline(),
|
||||
settings.StartingDirectory(),
|
||||
winrt::hstring{ cwd.c_str() },
|
||||
settings.StartingTitle(),
|
||||
envMap.GetView(),
|
||||
settings.InitialRows(),
|
||||
|
|
|
@ -81,7 +81,9 @@ namespace winrt::TerminalApp::implementation
|
|||
void ShowKeyboardServiceWarning();
|
||||
winrt::hstring KeyboardServiceDisabledText();
|
||||
|
||||
winrt::fire_and_forget ProcessStartupActions(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::ActionAndArgs> actions, const bool initial);
|
||||
winrt::fire_and_forget ProcessStartupActions(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::ActionAndArgs> actions,
|
||||
const bool initial,
|
||||
const winrt::hstring cwd = L"");
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring);
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include "pch.h"
|
||||
#include "../Remoting/Monarch.h"
|
||||
#include "../Remoting/CommandlineArgs.h"
|
||||
#include "../Remoting/FindTargetWindowArgs.h"
|
||||
#include "../Remoting/ProposeCommandlineResult.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace WEX::Logging;
|
||||
|
@ -32,6 +35,30 @@ using namespace winrt::Microsoft::Terminal;
|
|||
|
||||
namespace RemotingUnitTests
|
||||
{
|
||||
// This is a silly helper struct.
|
||||
// It will always throw an hresult_error on any of its methods.
|
||||
//
|
||||
// In the tests, it's hard to emulate a peasant process being totally dead
|
||||
// once the Monarch has captured a reference to it. Since everything's
|
||||
// in-proc in the tests, we can't decrement the refcount in such a way that
|
||||
// the monarch's reference will throw a catchable exception. Instead, this
|
||||
// class can be used to replace a peasant inside a Monarch, to emulate that
|
||||
// peasant process dying. Any time the monarch tries to do something to this
|
||||
// peasant, it'll throw an exception.
|
||||
struct DeadPeasant : implements<DeadPeasant, winrt::Microsoft::Terminal::Remoting::IPeasant>
|
||||
{
|
||||
DeadPeasant() = default;
|
||||
void AssignID(uint64_t /*id*/) { throw winrt::hresult_error{}; };
|
||||
uint64_t GetID() { throw winrt::hresult_error{}; };
|
||||
uint64_t GetPID() { throw winrt::hresult_error{}; };
|
||||
bool ExecuteCommandline(const Remoting::CommandlineArgs& /*args*/) { throw winrt::hresult_error{}; }
|
||||
void ActivateWindow(const Remoting::WindowActivatedArgs& /*args*/) { throw winrt::hresult_error{}; }
|
||||
Remoting::CommandlineArgs InitialArgs() { throw winrt::hresult_error{}; }
|
||||
Remoting::WindowActivatedArgs GetLastActivatedArgs() { throw winrt::hresult_error{}; }
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, Remoting::WindowActivatedArgs);
|
||||
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, Remoting::CommandlineArgs);
|
||||
};
|
||||
|
||||
class RemotingTests
|
||||
{
|
||||
BEGIN_TEST_CLASS(RemotingTests)
|
||||
|
@ -39,13 +66,60 @@ namespace RemotingUnitTests
|
|||
|
||||
TEST_METHOD(CreateMonarch);
|
||||
TEST_METHOD(CreatePeasant);
|
||||
TEST_METHOD(CreatePeasantWithNew);
|
||||
TEST_METHOD(AddPeasants);
|
||||
TEST_METHOD(GetPeasantsByID);
|
||||
TEST_METHOD(AddPeasantsToNewMonarch);
|
||||
TEST_METHOD(RemovePeasantFromMonarchWhenFreed);
|
||||
|
||||
TEST_METHOD(ProposeCommandlineNoWindow);
|
||||
TEST_METHOD(ProposeCommandlineGivenWindow);
|
||||
TEST_METHOD(ProposeCommandlineNegativeWindow);
|
||||
TEST_METHOD(ProposeCommandlineCurrentWindow);
|
||||
TEST_METHOD(ProposeCommandlineNonExistentWindow);
|
||||
TEST_METHOD(ProposeCommandlineDeadWindow);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _killPeasant(const com_ptr<Remoting::implementation::Monarch>& m,
|
||||
const uint64_t peasantID);
|
||||
|
||||
static void _findTargetWindowHelper(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
|
||||
};
|
||||
|
||||
// Helper to replace the specified peasant in a monarch with a
|
||||
// "DeadPeasant", which will emulate what happens when the peasant process
|
||||
// dies.
|
||||
void RemotingTests::_killPeasant(const com_ptr<Remoting::implementation::Monarch>& m,
|
||||
const uint64_t peasantID)
|
||||
{
|
||||
if (peasantID <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
com_ptr<DeadPeasant> tombstone;
|
||||
tombstone.attach(new DeadPeasant());
|
||||
m->_peasants[peasantID] = *tombstone;
|
||||
}
|
||||
|
||||
// Helper to get the first argument out of the commandline, and try to
|
||||
// convert it to an int.
|
||||
void RemotingTests::_findTargetWindowHelper(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args)
|
||||
{
|
||||
const auto arguments = args.Args().Commandline();
|
||||
if (arguments.size() > 0)
|
||||
{
|
||||
const auto index = std::stoi(arguments.at(0).c_str());
|
||||
args.ResultTargetWindow(index);
|
||||
}
|
||||
}
|
||||
|
||||
void RemotingTests::CreateMonarch()
|
||||
{
|
||||
auto m1 = winrt::make_self<Remoting::implementation::Monarch>();
|
||||
|
@ -84,4 +158,448 @@ namespace RemotingUnitTests
|
|||
L"A Peasant with an explicit PID should use the one we provided");
|
||||
}
|
||||
|
||||
void RemotingTests::CreatePeasantWithNew()
|
||||
{
|
||||
Log::Comment(L"The same thing as the above test, but with `new` instead of insanity on the stack");
|
||||
|
||||
auto p1 = winrt::make_self<Remoting::implementation::Peasant>();
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
VERIFY_ARE_EQUAL(GetCurrentProcessId(),
|
||||
p1->GetPID(),
|
||||
L"A Peasant without an explicit PID should use the current PID");
|
||||
|
||||
auto expectedFakePID = 2345u;
|
||||
|
||||
com_ptr<Remoting::implementation::Peasant> p2;
|
||||
VERIFY_IS_NULL(p2);
|
||||
p2.attach(new Remoting::implementation::Peasant(expectedFakePID));
|
||||
|
||||
VERIFY_IS_NOT_NULL(p2);
|
||||
VERIFY_ARE_EQUAL(expectedFakePID,
|
||||
p2->GetPID(),
|
||||
L"A Peasant with an explicit PID should use the one we provided");
|
||||
}
|
||||
|
||||
void RemotingTests::AddPeasants()
|
||||
{
|
||||
const auto monarch0PID = 12345u;
|
||||
const auto peasant1PID = 23456u;
|
||||
const auto peasant2PID = 34567u;
|
||||
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
|
||||
com_ptr<Remoting::implementation::Peasant> p2;
|
||||
p2.attach(new Remoting::implementation::Peasant(peasant2PID));
|
||||
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
VERIFY_IS_NOT_NULL(p2);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
void RemotingTests::GetPeasantsByID()
|
||||
{
|
||||
const auto monarch0PID = 12345u;
|
||||
const auto peasant1PID = 23456u;
|
||||
const auto peasant2PID = 34567u;
|
||||
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
|
||||
com_ptr<Remoting::implementation::Peasant> p2;
|
||||
p2.attach(new Remoting::implementation::Peasant(peasant2PID));
|
||||
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
VERIFY_IS_NOT_NULL(p2);
|
||||
|
||||
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());
|
||||
|
||||
auto maybeP1 = m0->_getPeasant(1);
|
||||
VERIFY_IS_NOT_NULL(maybeP1);
|
||||
VERIFY_ARE_EQUAL(peasant1PID, maybeP1.GetPID());
|
||||
|
||||
auto maybeP2 = m0->_getPeasant(2);
|
||||
VERIFY_IS_NOT_NULL(maybeP2);
|
||||
VERIFY_ARE_EQUAL(peasant2PID, maybeP2.GetPID());
|
||||
}
|
||||
|
||||
void RemotingTests::AddPeasantsToNewMonarch()
|
||||
{
|
||||
const auto monarch0PID = 12345u;
|
||||
const auto peasant1PID = 23456u;
|
||||
const auto peasant2PID = 34567u;
|
||||
const auto monarch3PID = 45678u;
|
||||
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
|
||||
com_ptr<Remoting::implementation::Peasant> p2;
|
||||
p2.attach(new Remoting::implementation::Peasant(peasant2PID));
|
||||
|
||||
com_ptr<Remoting::implementation::Monarch> m3;
|
||||
m3.attach(new Remoting::implementation::Monarch(monarch3PID));
|
||||
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
VERIFY_IS_NOT_NULL(p2);
|
||||
VERIFY_IS_NOT_NULL(m3);
|
||||
|
||||
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());
|
||||
|
||||
m3->AddPeasant(*p1);
|
||||
m3->AddPeasant(*p2);
|
||||
|
||||
VERIFY_ARE_EQUAL(1, p1->GetID());
|
||||
VERIFY_ARE_EQUAL(2, p2->GetID());
|
||||
}
|
||||
|
||||
void RemotingTests::RemovePeasantFromMonarchWhenFreed()
|
||||
{
|
||||
const auto monarch0PID = 12345u;
|
||||
const auto peasant1PID = 23456u;
|
||||
const auto peasant2PID = 34567u;
|
||||
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
|
||||
com_ptr<Remoting::implementation::Peasant> p2;
|
||||
p2.attach(new Remoting::implementation::Peasant(peasant2PID));
|
||||
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
VERIFY_IS_NOT_NULL(p2);
|
||||
|
||||
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());
|
||||
|
||||
Log::Comment(L"Kill peasant 1. Make sure that it gets removed from the monarch.");
|
||||
RemotingTests::_killPeasant(m0, p1->GetID());
|
||||
|
||||
auto maybeP2 = m0->_getPeasant(2);
|
||||
VERIFY_IS_NOT_NULL(maybeP2);
|
||||
VERIFY_ARE_EQUAL(peasant2PID, maybeP2.GetPID());
|
||||
|
||||
auto maybeP1 = m0->_getPeasant(1);
|
||||
VERIFY_IS_NULL(maybeP1);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, m0->_peasants.size());
|
||||
}
|
||||
|
||||
void RemotingTests::ProposeCommandlineNoWindow()
|
||||
{
|
||||
Log::Comment(L"Test proposing a commandline that doesn't have a window specified in it");
|
||||
|
||||
const auto monarch0PID = 12345u;
|
||||
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
m0->FindTargetWindowRequested(&RemotingTests::_findTargetWindowHelper);
|
||||
|
||||
std::vector<winrt::hstring> args{};
|
||||
Remoting::CommandlineArgs eventArgs{ { args }, { L"" } };
|
||||
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(true, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(false, (bool)result.Id());
|
||||
|
||||
Log::Comment(L"Add a peasant");
|
||||
const auto peasant1PID = 23456u;
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
m0->AddPeasant(*p1);
|
||||
|
||||
Log::Comment(L"Propose the same args again after adding a peasant - we should still return {create new window, no ID}");
|
||||
result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(true, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(false, (bool)result.Id());
|
||||
}
|
||||
|
||||
void RemotingTests::ProposeCommandlineGivenWindow()
|
||||
{
|
||||
Log::Comment(L"Test proposing a commandline for a window that currently exists");
|
||||
|
||||
const auto monarch0PID = 12345u;
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
m0->FindTargetWindowRequested(&RemotingTests::_findTargetWindowHelper);
|
||||
|
||||
Log::Comment(L"Add a peasant");
|
||||
const auto peasant1PID = 23456u;
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
m0->AddPeasant(*p1);
|
||||
|
||||
p1->ExecuteCommandlineRequested([&](auto&&, const Remoting::CommandlineArgs& cmdlineArgs) {
|
||||
Log::Comment(L"Commandline dispatched to p1");
|
||||
VERIFY_IS_GREATER_THAN(cmdlineArgs.Commandline().size(), 1u);
|
||||
VERIFY_ARE_EQUAL(L"arg[1]", cmdlineArgs.Commandline().at(1));
|
||||
});
|
||||
|
||||
std::vector<winrt::hstring> args{ L"1", L"arg[1]" };
|
||||
Remoting::CommandlineArgs eventArgs{ { args }, { L"" } };
|
||||
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(false, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(false, (bool)result.Id());
|
||||
}
|
||||
void RemotingTests::ProposeCommandlineNegativeWindow()
|
||||
{
|
||||
Log::Comment(L"Test proposing a commandline for an invalid window ID, like -1");
|
||||
|
||||
const auto monarch0PID = 12345u;
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
m0->FindTargetWindowRequested(&RemotingTests::_findTargetWindowHelper);
|
||||
|
||||
Log::Comment(L"Add a peasant");
|
||||
const auto peasant1PID = 23456u;
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
m0->AddPeasant(*p1);
|
||||
|
||||
{
|
||||
std::vector<winrt::hstring> args{ L"-1" };
|
||||
Remoting::CommandlineArgs eventArgs{ { args }, { L"" } };
|
||||
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(true, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(false, (bool)result.Id());
|
||||
}
|
||||
{
|
||||
std::vector<winrt::hstring> args{ L"-2" };
|
||||
Remoting::CommandlineArgs eventArgs{ { args }, { L"" } };
|
||||
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(true, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(false, (bool)result.Id());
|
||||
}
|
||||
}
|
||||
|
||||
void RemotingTests::ProposeCommandlineCurrentWindow()
|
||||
{
|
||||
Log::Comment(L"Test proposing a commandline for the current window (ID=0)");
|
||||
|
||||
const auto monarch0PID = 12345u;
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
m0->FindTargetWindowRequested(&RemotingTests::_findTargetWindowHelper);
|
||||
|
||||
Log::Comment(L"Add a peasant");
|
||||
const auto peasant1PID = 23456u;
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
m0->AddPeasant(*p1);
|
||||
p1->ExecuteCommandlineRequested([&](auto&&, const Remoting::CommandlineArgs& cmdlineArgs) {
|
||||
Log::Comment(L"Commandline dispatched to p1");
|
||||
VERIFY_IS_GREATER_THAN(cmdlineArgs.Commandline().size(), 1u);
|
||||
VERIFY_ARE_EQUAL(L"arg[1]", cmdlineArgs.Commandline().at(1));
|
||||
});
|
||||
|
||||
std::vector<winrt::hstring> p1Args{ L"0", L"arg[1]" };
|
||||
std::vector<winrt::hstring> p2Args{ L"0", L"this is for p2" };
|
||||
|
||||
{
|
||||
Log::Comment(L"Manually activate the first peasant");
|
||||
// This would usually happen immediately when the window is created, but
|
||||
// there's no actual window in these tests.
|
||||
Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(),
|
||||
winrt::guid{},
|
||||
winrt::clock().now() };
|
||||
p1->ActivateWindow(activatedArgs);
|
||||
|
||||
Remoting::CommandlineArgs eventArgs{ { p1Args }, { L"" } };
|
||||
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(false, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(false, (bool)result.Id());
|
||||
}
|
||||
|
||||
Log::Comment(L"Add a second peasant");
|
||||
const auto peasant2PID = 34567u;
|
||||
com_ptr<Remoting::implementation::Peasant> p2;
|
||||
p2.attach(new Remoting::implementation::Peasant(peasant2PID));
|
||||
VERIFY_IS_NOT_NULL(p2);
|
||||
m0->AddPeasant(*p2);
|
||||
p2->ExecuteCommandlineRequested([&](auto&&, const Remoting::CommandlineArgs& cmdlineArgs) {
|
||||
Log::Comment(L"Commandline dispatched to p2");
|
||||
VERIFY_IS_GREATER_THAN(cmdlineArgs.Commandline().size(), 1u);
|
||||
VERIFY_ARE_EQUAL(L"this is for p2", cmdlineArgs.Commandline().at(1));
|
||||
});
|
||||
|
||||
{
|
||||
Log::Comment(L"Activate the second peasant");
|
||||
Remoting::WindowActivatedArgs activatedArgs{ p2->GetID(),
|
||||
winrt::guid{},
|
||||
winrt::clock().now() };
|
||||
p2->ActivateWindow(activatedArgs);
|
||||
|
||||
Log::Comment(L"Send a commandline to the current window, which should be p2");
|
||||
Remoting::CommandlineArgs eventArgs{ { p2Args }, { L"" } };
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(false, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(false, (bool)result.Id());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Reactivate the first peasant");
|
||||
Remoting::WindowActivatedArgs activatedArgs{ p1->GetID(),
|
||||
winrt::guid{},
|
||||
winrt::clock().now() };
|
||||
p1->ActivateWindow(activatedArgs);
|
||||
|
||||
Log::Comment(L"Send a commandline to the current window, which should be p1 again");
|
||||
Remoting::CommandlineArgs eventArgs{ { p1Args }, { L"" } };
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(false, result.ShouldCreateWindow());
|
||||
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");
|
||||
|
||||
const auto monarch0PID = 12345u;
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
m0->FindTargetWindowRequested(&RemotingTests::_findTargetWindowHelper);
|
||||
|
||||
Log::Comment(L"Add a peasant");
|
||||
const auto peasant1PID = 23456u;
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
m0->AddPeasant(*p1);
|
||||
|
||||
{
|
||||
std::vector<winrt::hstring> args{ L"2" };
|
||||
Remoting::CommandlineArgs eventArgs{ { args }, { L"" } };
|
||||
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(true, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(true, (bool)result.Id());
|
||||
VERIFY_ARE_EQUAL(2u, result.Id().Value());
|
||||
}
|
||||
{
|
||||
std::vector<winrt::hstring> args{ L"10" };
|
||||
Remoting::CommandlineArgs eventArgs{ { args }, { L"" } };
|
||||
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(true, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(true, (bool)result.Id());
|
||||
VERIFY_ARE_EQUAL(10u, result.Id().Value());
|
||||
}
|
||||
}
|
||||
|
||||
void RemotingTests::ProposeCommandlineDeadWindow()
|
||||
{
|
||||
Log::Comment(L"Test proposing a commandline for a peasant that previously died");
|
||||
|
||||
const auto monarch0PID = 12345u;
|
||||
com_ptr<Remoting::implementation::Monarch> m0;
|
||||
m0.attach(new Remoting::implementation::Monarch(monarch0PID));
|
||||
VERIFY_IS_NOT_NULL(m0);
|
||||
m0->FindTargetWindowRequested(&RemotingTests::_findTargetWindowHelper);
|
||||
|
||||
Log::Comment(L"Add a peasant");
|
||||
const auto peasant1PID = 23456u;
|
||||
com_ptr<Remoting::implementation::Peasant> p1;
|
||||
p1.attach(new Remoting::implementation::Peasant(peasant1PID));
|
||||
VERIFY_IS_NOT_NULL(p1);
|
||||
m0->AddPeasant(*p1);
|
||||
p1->ExecuteCommandlineRequested([&](auto&&, const Remoting::CommandlineArgs& /*cmdlineArgs*/) {
|
||||
Log::Comment(L"Commandline dispatched to p1");
|
||||
VERIFY_IS_TRUE(false, L"This should not happen, this peasant should be dead.");
|
||||
});
|
||||
|
||||
Log::Comment(L"Add a second peasant");
|
||||
const auto peasant2PID = 34567u;
|
||||
com_ptr<Remoting::implementation::Peasant> p2;
|
||||
p2.attach(new Remoting::implementation::Peasant(peasant2PID));
|
||||
VERIFY_IS_NOT_NULL(p2);
|
||||
m0->AddPeasant(*p2);
|
||||
p2->ExecuteCommandlineRequested([&](auto&&, const Remoting::CommandlineArgs& cmdlineArgs) {
|
||||
Log::Comment(L"Commandline dispatched to p2");
|
||||
VERIFY_IS_GREATER_THAN(cmdlineArgs.Commandline().size(), 1u);
|
||||
VERIFY_ARE_EQUAL(L"this is for p2", cmdlineArgs.Commandline().at(1));
|
||||
});
|
||||
|
||||
std::vector<winrt::hstring> p1Args{ L"1", L"arg[1]" };
|
||||
std::vector<winrt::hstring> p2Args{ L"2", L"this is for p2" };
|
||||
|
||||
Log::Comment(L"Kill peasant 1");
|
||||
|
||||
_killPeasant(m0, 1);
|
||||
|
||||
{
|
||||
Log::Comment(L"Send a commandline to p2, who is still alive. We won't create a new window.");
|
||||
|
||||
Remoting::CommandlineArgs eventArgs{ { p2Args }, { L"" } };
|
||||
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(false, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(false, (bool)result.Id());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Send a commandline to p1, who is dead. We will create a new window.");
|
||||
Remoting::CommandlineArgs eventArgs{ { p1Args }, { L"" } };
|
||||
|
||||
auto result = m0->ProposeCommandline(eventArgs);
|
||||
VERIFY_ARE_EQUAL(true, result.ShouldCreateWindow());
|
||||
VERIFY_ARE_EQUAL(true, (bool)result.Id());
|
||||
VERIFY_ARE_EQUAL(1u, result.Id().Value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,12 @@ AppHost::AppHost() noexcept :
|
|||
{
|
||||
_logic = _app.Logic(); // get a ref to app's logic
|
||||
|
||||
// Inform the WindowManager that it can use us to find the target window for
|
||||
// a set of commandline args. This needs to be done before
|
||||
// _HandleCommandlineArgs, because WE might end up being the monarch. That
|
||||
// would mean we'd need to be responsible for looking that up.
|
||||
_windowManager.FindTargetWindowRequested({ this, &AppHost::_FindTargetWindow });
|
||||
|
||||
// If there were commandline args to our process, try and process them here.
|
||||
// Do this before AppLogic::Create, otherwise this will have no effect.
|
||||
//
|
||||
|
@ -64,6 +70,7 @@ AppHost::AppHost() noexcept :
|
|||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
|
||||
_window->WindowActivated({ this, &AppHost::_WindowActivated });
|
||||
_window->SetAlwaysOnTop(_logic.GetInitialAlwaysOnTop());
|
||||
_window->MakeWindow();
|
||||
}
|
||||
|
@ -161,7 +168,7 @@ void AppHost::_HandleCommandlineArgs()
|
|||
{
|
||||
if (auto args{ peasant.InitialArgs() })
|
||||
{
|
||||
const auto result = _logic.SetStartupCommandline(args.Args());
|
||||
const auto result = _logic.SetStartupCommandline(args.Commandline());
|
||||
const auto message = _logic.ParseCommandlineMessage();
|
||||
if (!message.empty())
|
||||
{
|
||||
|
@ -188,12 +195,6 @@ void AppHost::_HandleCommandlineArgs()
|
|||
// use to send the actions to the app.
|
||||
peasant.ExecuteCommandlineRequested({ this, &AppHost::_DispatchCommandline });
|
||||
}
|
||||
|
||||
// TODO:projects/5 if we end up not creating a new window, we crash. I'm
|
||||
// thinking this is because the XAML host is not happy about being torn
|
||||
// down before it has a chance to do really anything. Is there some way
|
||||
// to get the app logic without instantiating the entire app? or at
|
||||
// least the parts we'll need for remoting?
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -516,8 +517,51 @@ bool AppHost::HasWindow()
|
|||
return _shouldCreateWindow;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for the Peasant::ExecuteCommandlineRequested event. Take the
|
||||
// provided commandline args, and attempt to parse them and perform the
|
||||
// actions immediately. The parsing is performed by AppLogic.
|
||||
// - This is invoked when another wt.exe instance runs something like `wt -w 1
|
||||
// new-tab`, and the Monarch delegates the commandline to this instance.
|
||||
// Arguments:
|
||||
// - args: the bundle of a commandline and working directory to use for this invocation.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppHost::_DispatchCommandline(winrt::Windows::Foundation::IInspectable /*sender*/,
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs args)
|
||||
Remoting::CommandlineArgs args)
|
||||
{
|
||||
_logic.ExecuteCommandline(args.Args());
|
||||
_logic.ExecuteCommandline(args.Commandline(), args.CurrentDirectory());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for the WindowManager::FindTargetWindowRequested event. The
|
||||
// manager will ask us how to figure out what the target window is for a set
|
||||
// of commandline arguments. We'll take those arguments, and ask AppLogic to
|
||||
// parse them for us. We'll then set ResultTargetWindow in the given args, so
|
||||
// the sender can use that result.
|
||||
// Arguments:
|
||||
// - args: the bundle of a commandline and working directory to find the correct target window for.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppHost::_FindTargetWindow(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const Remoting::FindTargetWindowArgs& args)
|
||||
{
|
||||
const auto targetWindow = _logic.FindTargetWindow(args.Args().Commandline());
|
||||
args.ResultTargetWindow(targetWindow);
|
||||
}
|
||||
|
||||
winrt::fire_and_forget AppHost::_WindowActivated()
|
||||
{
|
||||
co_await winrt::resume_background();
|
||||
|
||||
if (auto peasant{ _windowManager.CurrentWindow() })
|
||||
{
|
||||
// TODO: projects/5 - in the future, we'll want to actually get the
|
||||
// desktop GUID in IslandWindow, and bubble that up here, then down to
|
||||
// the Peasant. For now, we're just leaving space for it.
|
||||
Remoting::WindowActivatedArgs args{ peasant.GetID(),
|
||||
winrt::guid{},
|
||||
winrt::clock().now() };
|
||||
peasant.ActivateWindow(args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,11 @@ private:
|
|||
void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& arg);
|
||||
void _WindowMouseWheeled(const til::point coord, const int32_t delta);
|
||||
winrt::fire_and_forget _WindowActivated();
|
||||
|
||||
void _DispatchCommandline(winrt::Windows::Foundation::IInspectable sender,
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs args);
|
||||
|
||||
void _FindTargetWindow(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
|
||||
};
|
||||
|
|
|
@ -370,6 +370,15 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
|
|||
return 0; // eat the message
|
||||
}
|
||||
}
|
||||
case WM_ACTIVATE:
|
||||
{
|
||||
// wparam = 0 indicates the window was deactivated
|
||||
if (LOWORD(wparam) != 0)
|
||||
{
|
||||
_WindowActivatedHandlers();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_NCLBUTTONDOWN:
|
||||
case WM_NCLBUTTONUP:
|
||||
|
|
|
@ -43,6 +43,7 @@ public:
|
|||
DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>);
|
||||
DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>);
|
||||
WINRT_CALLBACK(MouseScrolled, winrt::delegate<void(til::point, int32_t)>);
|
||||
WINRT_CALLBACK(WindowActivated, winrt::delegate<void()>);
|
||||
|
||||
protected:
|
||||
void ForceResize()
|
||||
|
|
|
@ -126,7 +126,11 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
|
|||
AppHost host;
|
||||
if (!host.HasWindow())
|
||||
{
|
||||
return 0;
|
||||
// If we were told to not have a window, exit early. Make sure to use
|
||||
// ExitProcess to die here. If you try just `return 0`, then
|
||||
// the XAML app host will crash during teardown. ExitProcess avoids
|
||||
// that.
|
||||
ExitProcess(0);
|
||||
}
|
||||
|
||||
// Initialize the xaml content. This must be called AFTER the
|
||||
|
|
Loading…
Reference in New Issue