terminal/src/tools/MonarchPeasantSample/main.cpp
Mike Griese c33a97955f
Add a Monarch/Peasant sample app (#8171)
This PR adds a sample monarch/peasant application. This is a type of
application where a single "Monarch" can coordinate the actions of multiple
other "Peasant" processes, as described by the specs in #7240 and #8135.

This project is intended to be a standalone sample of how the architecture would
work, without involving the entirety of the Windows Terminal build. Eventually,
this architecture will be incorporated into `wt.exe` itself, to enable scenarios
like:
* Run `wt` in the current window (#4472)
* Single Instance Mode (#2227)

For an example of this sample running, see the below GIF:

![monarch-peasant-sample-001](https://user-images.githubusercontent.com/18356694/98262202-f39b1500-1f4a-11eb-9220-4af4d922339f.gif)

This sample operates largely by printing to the console, to help the reader
understand how it's working through its logic.

I'm doing this mostly so we can have a _committed_ sample of this type of application, kinda like how VtPipeTerm is a sample ConPTY application. It's a lot easier to understand (& build on) when there aren't any window shenanigans, settings loading, Island instantiation, or anything else that the whole of `WindowsTerminal.exe` needs

* [x] I work here
* [x] This is sample code, so I'm not shipping tests for it.
* [x] Go see the doc over in #8135
2021-01-19 21:55:30 +00:00

169 lines
5.1 KiB
C++

#include "pch.h"
#include "AppState.h"
#include "../../types/inc/utils.hpp"
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace ::Microsoft::Console;
////////////////////////////////////////////////////////////////////////////////
// This seems like a hack, but it works.
//
// This class factory works so that there's only ever one instance of a Monarch
// per-process. Once the first monarch is created, we'll stash it in g_weak.
// Future callers who try to instantiate a Monarch will get the one that's
// already been made.
//
// I'm sure there's a better way to do this with WRL, but I'm not familiar
// enough with WRL to know for sure.
winrt::weak_ref<MonarchPeasantSample::implementation::Monarch> g_weak{ nullptr };
struct MonarchFactory : implements<MonarchFactory, IClassFactory>
{
MonarchFactory() = default;
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept final
{
*result = nullptr;
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
if (!g_weak)
{
// Create a new Monarch instance
auto strong = make_self<MonarchPeasantSample::implementation::Monarch>();
g_weak = (*strong).get_weak();
return strong.as(iid, result);
}
else
{
// We already instantiated one Monarch, let's just return that one!
auto strong = g_weak.get();
return strong.as(iid, result);
}
}
HRESULT __stdcall LockServer(BOOL) noexcept final
{
return S_OK;
}
};
////////////////////////////////////////////////////////////////////////////////
// Function Description:
// - Register the Monarch object with COM. This allows other processes to create
// Monarch's in our process space with CoCreateInstance and the Monarch_clsid.
DWORD registerAsMonarch()
{
DWORD registrationHostClass{};
check_hresult(CoRegisterClassObject(Monarch_clsid,
make<MonarchFactory>().get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&registrationHostClass));
return registrationHostClass;
}
// Function Description:
// - Called when the old monarch dies. Create a new connection to the new
// monarch. This might be us! If we're the new monarch, then update the
// Monarch to know which Peasant it came from. Otherwise, tell the new monarch
// that we exist.
void electNewMonarch(AppState& state)
{
state.monarch = AppState::instantiateMonarch();
bool isMonarch = state.areWeTheKing(true);
printf("LONG LIVE THE %sKING\x1b[m\n", isMonarch ? "\x1b[33m" : "");
if (isMonarch)
{
state.remindKingWhoTheyAre(state.peasant);
}
else
{
// Add us to the new monarch
state.monarch.AddPeasant(state.peasant);
}
}
void appLoop(AppState& state)
{
auto dwRegistration = registerAsMonarch();
// IMPORTANT! Tear down the registration as soon as we exit. If we're not a
// real peasant window (the monarch passed our commandline to someone else),
// then the monarch dies, we don't want our registration becoming the active
// monarch!
auto cleanup = wil::scope_exit([&]() {
check_hresult(CoRevokeClassObject(dwRegistration));
});
// Tricky - first, we have to ask the monarch to handle the commandline.
// They will tell us if we need to create a peasant.
state.createMonarch();
// processCommandline will return true if we should exit early.
if (state.processCommandline())
{
return;
}
bool isMonarch = state.areWeTheKing(true);
bool exitRequested = false;
// (monarch|peasant)AppLoop will return when they've run to completion. If
// they return true, then just exit the application (the user might have
// pressed 'q' to exit). If the peasant returns false, then it detected the
// monarch died. Attempt to elect a new one.
while (!exitRequested)
{
if (isMonarch)
{
exitRequested = monarchAppLoop(state);
}
else
{
exitRequested = peasantAppLoop(state);
if (!exitRequested)
{
electNewMonarch(state);
isMonarch = state.areWeTheKing(false);
}
}
}
}
int main(int argc, char** argv)
{
AppState state;
state.initializeState();
// Collect up all the commandline arguments
printf("args:[");
for (auto& elem : wil::make_range(argv, argc))
{
printf("%s, ", elem);
// This is obviously a bad way of converting args to a vector of
// hstrings, but it'll do for now.
state.args.emplace_back(winrt::to_hstring(elem));
}
printf("]\n");
try
{
appLoop(state);
}
catch (hresult_error const& e)
{
printf("Error: %ls\n", e.message().c_str());
}
printf("We've left the app. Press any key to close.\n");
const auto ch = _getch();
ch;
printf("Exiting client\n");
}