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
|
@ -40,6 +40,7 @@ ITab
|
|||
ITaskbar
|
||||
LCID
|
||||
llabs
|
||||
llu
|
||||
localtime
|
||||
lround
|
||||
LSHIFT
|
||||
|
@ -61,6 +62,7 @@ PAGESCROLL
|
|||
REGCLS
|
||||
pmr
|
||||
RETURNCMD
|
||||
REGCLS
|
||||
rfind
|
||||
roundf
|
||||
RSHIFT
|
||||
|
|
328
OpenConsole.sln
|
@ -21,7 +21,8 @@
|
|||
"/res/terminal/",
|
||||
"/doc/specs/",
|
||||
"/doc/cascadia/",
|
||||
"/doc/user-docs/"
|
||||
"/doc/user-docs/",
|
||||
"/src/tools/MonarchPeasantSample/",
|
||||
],
|
||||
"SuffixFilters": [
|
||||
".dbb",
|
||||
|
@ -38,5 +39,5 @@
|
|||
".rec",
|
||||
".err",
|
||||
".xlsx"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.1 KiB |
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'">
|
||||
<VisualStudioVersion>15.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x86">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x86">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|AnyCPU">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|AnyCPU">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>f75e29d0-d288-478b-8d83-2c190f321a3f</ProjectGuid>
|
||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
|
||||
<EntryPointProjectUniqueName>..\MonarchPeasantSample\MonarchPeasantSample.vcxproj</EntryPointProjectUniqueName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Images\SplashScreen.scale-200.png" />
|
||||
<Content Include="Images\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Images\StoreLogo.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MonarchPeasantSample\MonarchPeasantSample.vcxproj" >
|
||||
<!--
|
||||
THESE PROPERTIES ARE LOAD BEARING!
|
||||
|
||||
We need them so the MonarchPeasantSample.winmd will be placed in the
|
||||
package root. If it's not there, then we won't be able to activate our
|
||||
WinRT classes!
|
||||
-->
|
||||
<Private>true</Private>
|
||||
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="5d4b020d-14a7-4a1a-a359-3f468e23bae3"
|
||||
Publisher="CN=migrie"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>MonarchPeasantPackage</DisplayName>
|
||||
<PublisherDisplayName>migrie</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="MonarchPeasantPackage"
|
||||
Description="MonarchPeasantPackage"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.appExecutionAlias" Executable="MonarchPeasantSample\MonarchPeasantSample.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="MonarchPeasantSample.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
</uap3:Extension>
|
||||
|
||||
</Extensions>
|
||||
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
|
@ -0,0 +1,119 @@
|
|||
#include "pch.h"
|
||||
#include "AppState.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
void AppState::_setupConsole()
|
||||
{
|
||||
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
hInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
GetConsoleMode(hOutput, &dwMode);
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
SetConsoleMode(hOutput, dwMode);
|
||||
}
|
||||
|
||||
void AppState::initializeState()
|
||||
{
|
||||
// Initialize the console handles
|
||||
_setupConsole();
|
||||
|
||||
// Set up WinRT
|
||||
init_apartment();
|
||||
}
|
||||
|
||||
bool AppState::areWeTheKing(const bool logPIDs)
|
||||
{
|
||||
auto kingPID = monarch.GetPID();
|
||||
auto ourPID = GetCurrentProcessId();
|
||||
if (logPIDs)
|
||||
{
|
||||
if (ourPID == kingPID)
|
||||
{
|
||||
printf(fmt::format("We're the\x1b[33m king\x1b[m - our PID is {}\n", ourPID).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(fmt::format("We're a lowly peasant - the king is {}\n", kingPID).c_str());
|
||||
}
|
||||
}
|
||||
return (ourPID == kingPID);
|
||||
}
|
||||
|
||||
void AppState::remindKingWhoTheyAre(const winrt::MonarchPeasantSample::IPeasant& iPeasant)
|
||||
{
|
||||
winrt::com_ptr<MonarchPeasantSample::implementation::Monarch> monarchImpl;
|
||||
monarchImpl.copy_from(winrt::get_self<MonarchPeasantSample::implementation::Monarch>(monarch));
|
||||
if (monarchImpl)
|
||||
{
|
||||
auto ourID = iPeasant.GetID();
|
||||
monarchImpl->SetSelfID(ourID);
|
||||
monarchImpl->AddPeasant(iPeasant);
|
||||
printf("The king is peasant #%lld\n", ourID);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Shoot, we wanted to be able to get the monarchImpl here but couldnt\n");
|
||||
}
|
||||
}
|
||||
|
||||
winrt::MonarchPeasantSample::Monarch AppState::instantiateMonarch()
|
||||
{
|
||||
// Heads up! This only works because we're using
|
||||
// "metadata-based-marshalling" for our WinRT types. THat means the OS is
|
||||
// using the .winmd file we generate to figure out the proxy/stub
|
||||
// definitions for our types automatically. This only works in the following
|
||||
// cases:
|
||||
//
|
||||
// * If we're running unpackaged: the .winmd but be a sibling of the .exe
|
||||
// * If we're running packaged: the .winmd must be in the package root
|
||||
auto monarch = create_instance<winrt::MonarchPeasantSample::Monarch>(Monarch_clsid,
|
||||
CLSCTX_LOCAL_SERVER);
|
||||
return monarch;
|
||||
}
|
||||
|
||||
MonarchPeasantSample::IPeasant AppState::_createOurPeasant()
|
||||
{
|
||||
auto peasant = winrt::make_self<MonarchPeasantSample::implementation::Peasant>();
|
||||
auto ourID = monarch.AddPeasant(*peasant);
|
||||
printf("The monarch assigned us the ID %llu\n", ourID);
|
||||
|
||||
if (areWeTheKing())
|
||||
{
|
||||
remindKingWhoTheyAre(*peasant);
|
||||
}
|
||||
|
||||
return *peasant;
|
||||
}
|
||||
|
||||
void AppState::createMonarch()
|
||||
{
|
||||
monarch = AppState::instantiateMonarch();
|
||||
}
|
||||
|
||||
// return true to exit early, false if we should continue into the main loop
|
||||
bool AppState::processCommandline()
|
||||
{
|
||||
const bool isKing = areWeTheKing(false);
|
||||
// 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
|
||||
const bool createNewWindow = isKing || monarch.ProposeCommandline({ args }, { L"placeholder CWD" });
|
||||
|
||||
if (createNewWindow)
|
||||
{
|
||||
peasant = _createOurPeasant();
|
||||
peasant.ExecuteCommandline({ args }, { L"placeholder CWD" });
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("The Monarch instructed us to not create a new window. We'll be exiting now.\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
#include "SampleMonarch.h"
|
||||
#include "SamplePeasant.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
class AppState
|
||||
{
|
||||
public:
|
||||
bool areWeTheKing(const bool logPIDs = false);
|
||||
void initializeState();
|
||||
|
||||
static winrt::MonarchPeasantSample::Monarch instantiateMonarch();
|
||||
|
||||
void createMonarch();
|
||||
bool processCommandline();
|
||||
void remindKingWhoTheyAre(const winrt::MonarchPeasantSample::IPeasant& peasant);
|
||||
|
||||
HANDLE hInput{ INVALID_HANDLE_VALUE };
|
||||
HANDLE hOutput{ INVALID_HANDLE_VALUE };
|
||||
winrt::MonarchPeasantSample::IPeasant peasant{ nullptr };
|
||||
winrt::MonarchPeasantSample::Monarch monarch{ nullptr };
|
||||
std::vector<winrt::hstring> args;
|
||||
|
||||
private:
|
||||
void _setupConsole();
|
||||
int _appLoop();
|
||||
|
||||
winrt::MonarchPeasantSample::IPeasant _createOurPeasant();
|
||||
};
|
||||
|
||||
bool monarchAppLoop(AppState& state); // Defined in MonarchMain.cpp
|
||||
bool peasantAppLoop(AppState& state); // Defined in PeasantMain.cpp
|
|
@ -0,0 +1,38 @@
|
|||
#include "pch.h"
|
||||
#include "AppState.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
void printPeasants(const winrt::MonarchPeasantSample::Monarch& /*monarch*/)
|
||||
{
|
||||
printf("This is unimplemented\n");
|
||||
}
|
||||
|
||||
bool monarchAppLoop(AppState& state)
|
||||
{
|
||||
bool exitRequested = false;
|
||||
printf("Press `l` to list peasants, 'm' to change modes `q` to quit\n");
|
||||
|
||||
winrt::com_ptr<winrt::MonarchPeasantSample::implementation::Monarch> monarchImpl;
|
||||
monarchImpl.copy_from(winrt::get_self<winrt::MonarchPeasantSample::implementation::Monarch>(state.monarch));
|
||||
|
||||
while (!exitRequested)
|
||||
{
|
||||
const auto ch = _getch();
|
||||
if (ch == 'l')
|
||||
{
|
||||
printPeasants(state.monarch);
|
||||
}
|
||||
else if (ch == 'q')
|
||||
{
|
||||
exitRequested = true;
|
||||
}
|
||||
else if (ch == 'm')
|
||||
{
|
||||
if (monarchImpl)
|
||||
{
|
||||
monarchImpl->ToggleWindowingBehavior();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{21b7ea5e-1ef8-49b6-ac07-11714af0e37d}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>MonarchPeasantSample</RootNamespace>
|
||||
<ProjectName>MonarchPeasantSample</ProjectName>
|
||||
<TargetName>MonarchPeasantSample</TargetName>
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<OpenConsoleUniversalApp>false</OpenConsoleUniversalApp>
|
||||
<ApplicationType>Windows Store</ApplicationType>
|
||||
<TargetPlatformIdentifier>Windows</TargetPlatformIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateManifest>true</GenerateManifest>
|
||||
<EmbedManifest>true</EmbedManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Source Files -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="AppState.h" />
|
||||
<ClInclude Include="SampleMonarch.h">
|
||||
<DependentUpon>SampleMonarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SamplePeasant.h">
|
||||
<DependentUpon>SamplePeasant.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="AppState.cpp" />
|
||||
<ClCompile Include="MonarchMain.cpp" />
|
||||
<ClCompile Include="PeasantMain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SampleMonarch.cpp">
|
||||
<DependentUpon>SampleMonarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SamplePeasant.cpp">
|
||||
<DependentUpon>SamplePeasant.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Midl Include="SampleMonarch.idl" />
|
||||
<Midl Include="SamplePeasant.idl" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
This ItemGroup and the Globals PropertyGroup below it are required in order
|
||||
to enable F5 debugging for the unpackaged application
|
||||
-->
|
||||
<ItemGroup>
|
||||
<PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_general.xml" />
|
||||
<PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_local_windows.xml" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<!-- These have to come after post.props because the Cpp common targets will inexplicably overwrite them. -->
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,108 @@
|
|||
#include "pch.h"
|
||||
#include "AppState.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
bool peasantReadInput(AppState& state)
|
||||
{
|
||||
DWORD cNumRead, i;
|
||||
std::array<INPUT_RECORD, 128> irInBuf;
|
||||
|
||||
if (!ReadConsoleInput(state.hInput, // input buffer handle
|
||||
irInBuf.data(), // buffer to read into
|
||||
static_cast<DWORD>(irInBuf.size()), // size of read buffer
|
||||
&cNumRead)) // number of records read
|
||||
{
|
||||
printf("\x1b[31mReadConsoleInput failed\x1b[m\n");
|
||||
ExitProcess(0);
|
||||
}
|
||||
for (i = 0; i < cNumRead; i++)
|
||||
{
|
||||
switch (irInBuf[i].EventType)
|
||||
{
|
||||
case KEY_EVENT: // keyboard input
|
||||
{
|
||||
auto key = irInBuf[i].Event.KeyEvent;
|
||||
if (key.bKeyDown == false &&
|
||||
key.uChar.UnicodeChar == L'q')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("This window was activated\n");
|
||||
winrt::com_ptr<winrt::MonarchPeasantSample::implementation::Peasant> peasantImpl;
|
||||
peasantImpl.copy_from(winrt::get_self<winrt::MonarchPeasantSample::implementation::Peasant>(state.peasant));
|
||||
if (peasantImpl)
|
||||
{
|
||||
peasantImpl->raiseActivatedEvent();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MOUSE_EVENT:
|
||||
case WINDOW_BUFFER_SIZE_EVENT:
|
||||
case FOCUS_EVENT:
|
||||
case MENU_EVENT:
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("\x1b[33mUnknown event from ReadConsoleInput - this is probably impossible\x1b[m\n");
|
||||
ExitProcess(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true if we want to just exit the application.
|
||||
// Returns false if the monarch dies, and we need to elect a new one.
|
||||
bool peasantAppLoop(AppState& state)
|
||||
{
|
||||
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS, FALSE, static_cast<DWORD>(state.monarch.GetPID())) };
|
||||
if (hMonarch.get() == nullptr)
|
||||
{
|
||||
const auto gle = GetLastError();
|
||||
printf("\x1b[31mFailed to open the monarch process, error was %d\x1b[m\n", gle);
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE handlesToWaitOn[2]{ hMonarch.get(), state.hInput };
|
||||
|
||||
bool exitRequested = false;
|
||||
printf("Press `q` to quit\n");
|
||||
|
||||
while (!exitRequested)
|
||||
{
|
||||
auto waitResult = WaitForMultipleObjects(2, handlesToWaitOn, false, INFINITE);
|
||||
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0 + 0: // handlesToWaitOn[0] was signaled
|
||||
printf("THE KING IS \x1b[31mDEAD\x1b[m\n");
|
||||
// Return false here - this will trigger us to find the new monarch
|
||||
return false;
|
||||
|
||||
case WAIT_OBJECT_0 + 1: // handlesToWaitOn[1] was signaled
|
||||
exitRequested = peasantReadInput(state);
|
||||
break;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
printf("Wait timed out. This should be impossible.\n");
|
||||
break;
|
||||
|
||||
// Return value is invalid.
|
||||
default:
|
||||
{
|
||||
auto gle = GetLastError();
|
||||
printf("WaitForMultipleObjects returned: %d\n", waitResult);
|
||||
printf("Wait error: %d\n", gle);
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("Bottom of peasantAppLoop\n");
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Label="PropertySheets" />
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<!--
|
||||
To customize common C++/WinRT project properties:
|
||||
* right-click the project node
|
||||
* expand the Common Properties item
|
||||
* select the C++/WinRT property page
|
||||
|
||||
For more advanced scenarios, and complete documentation, please see:
|
||||
https://github.com/Microsoft/cppwinrt/tree/master/nuget
|
||||
-->
|
||||
<PropertyGroup />
|
||||
<ItemDefinitionGroup />
|
||||
</Project>
|
|
@ -0,0 +1,114 @@
|
|||
# Monarch/Peasant Sample
|
||||
|
||||
This directory contains 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:
|
||||
|
||||
![Gif of the MonarchPeasantSample](monarch-peasant-sample-000.gif)
|
||||
|
||||
This sample operates largely by printing to the console, to help the reader
|
||||
understand how it's working through its logic.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
MonarchPeasantSample.exe [--session,-s session-id] [args...]
|
||||
```
|
||||
|
||||
This will run a new instance of the MonarchPeasantSample.
|
||||
|
||||
One of the running `MonarchPeasantSample` processes will be the "Monarch",
|
||||
responsible for coordinating the creation of windows between processes. It can
|
||||
be toggled between different `glomToLastWindow` modes by pressing `m`. It can be
|
||||
exited with `q`.
|
||||
|
||||
The other instances will be "peasants", who can be told to execute commandlines.
|
||||
`q` will exit the process. Any other keypress will "activate" the peasant
|
||||
window, informing the monarch that this peasant is the newly-active one.
|
||||
|
||||
If the `session-id` is provided on the commandline, then the monarch will try to
|
||||
pass the provided `args` to the specified peasant process, accounting for the
|
||||
current value of `glomToLastWindow`. See the spec in [#8135] for details of how
|
||||
this argument works. The actual `args...` params are unused.
|
||||
|
||||
## Project layout
|
||||
|
||||
The code is vaguely separated into the following files, with the following
|
||||
purposes. As this code isn't production-ready code, the layering isn't
|
||||
particularly well organized nor enforced.
|
||||
|
||||
* `Monarch.idl/.h/.cpp`: Code for the WinRT Monarch object, responsible for
|
||||
coordinating the Peasants
|
||||
* `Peasant.idl/.h/.cpp`: Code for the WinRT Peasant object, which represents any
|
||||
individual application instance.
|
||||
* `AppState.h/.cpp`: This file contains some common state that's used throughout
|
||||
the application, in order to help encapsulate it all in one place.
|
||||
* `MonarchMain.cpp`: This file contains the main loop for the monarch process.
|
||||
It needs to be able to process console input, and additionally wait on the
|
||||
peasants, to know when they've died.
|
||||
* `PeasantMain.cpp`: This file contains the main loop for the peasant process.
|
||||
It needs to be able to wait on both console input, and on the Monarch process,
|
||||
to be able to determine who the next monarch should be.
|
||||
|
||||
## Remaining TODOs
|
||||
|
||||
This project represents an _incomplete_ example, but one that covers enough of
|
||||
the edge cases for the reader to comprehend the architecture. Below is a list of
|
||||
things that are missing from the sample:
|
||||
|
||||
* [ ] The Monarch should store a stack for the MRU peasant, not just the single
|
||||
MRU one
|
||||
* [ ] The Monarch needs to `WaitForMultipleObjects` on Peasants, to remove them
|
||||
from the map when they die
|
||||
* [ ] After an "election", the entire MRU window state is lost, because it was
|
||||
only stored in the current monarch. This needs to be distributed to all the
|
||||
other Peasants when it changes.
|
||||
* [ ] Theoretically, `ProposeCommandline` should use the CWD of the process
|
||||
calling it. That's left unimplemented for brevity.
|
||||
* [ ] Technically, the Monarch is also a Peasant, and any keypress should also
|
||||
activate the Monarch window as the MRU one.
|
||||
* [ ] We're storing a strictly-increasing int for to determine what the next ID
|
||||
for a peasant should be. This seems silly, and like we could probably just
|
||||
iterate to find the first gap in the map (I'm sure there's a better way of
|
||||
doing this that I can't recall, don't tell my CS540 professor)
|
||||
* [ ] The following steps causes an unexpected crash:
|
||||
- Create a monarch(#1) & peasant(#2)
|
||||
- activate the peasant(#2)
|
||||
- exit the peasant(#2)
|
||||
- try running `MonarchPeasantSample.exe -s 0` (or `-s 2`)
|
||||
- THIS WILL FAIL, but it _should_ just run the commandline in the monarch
|
||||
(in the case of `-s 0`) or in a new window (in the `-s 1` case)
|
||||
- The reason this fails is because the Monarch tries to call
|
||||
`Peasant::ExecuteCommandline`, but the Peasant object doesn't actually
|
||||
exist anymore (the process is dead!). Three fixes will help here:
|
||||
- wrap all calls to peasants with try/catch's
|
||||
- Do the "keep a MRU stack" thing (so we know `-s 0` is now the monarch)
|
||||
- Do the "wait on Peasant processes" thing, to know to pop #2 from the
|
||||
map and MRU stack.
|
||||
|
||||
## Utilities
|
||||
|
||||
Use the following helper script to open a new Terminal window with 4 splits - 3
|
||||
running instances of MonarchPeasantSample.exe, and a fourth which can be used to
|
||||
issue commands.
|
||||
|
||||
```cmd
|
||||
pushd %OPENCON%\bin\x64\Debug\MonarchPeasantSample
|
||||
wt -d . cmd /k MonarchPeasantSample.exe ; sp -d . cmd /k MonarchPeasantSample.exe ; sp -d . cmd /k MonarchPeasantSample.exe ; sp -d .
|
||||
popd
|
||||
|
||||
```
|
||||
|
||||
[#2227]: https://github.com/microsoft/terminal/issues/2227
|
||||
[#4472]: https://github.com/microsoft/terminal/issues/4472
|
||||
[#7240]: https://github.com/microsoft/terminal/pull/7240
|
||||
[#8135]: https://github.com/microsoft/terminal/pull/8135
|
|
@ -0,0 +1,186 @@
|
|||
#include "pch.h"
|
||||
#include "SampleMonarch.h"
|
||||
|
||||
#include "Monarch.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
namespace winrt::MonarchPeasantSample::implementation
|
||||
{
|
||||
Monarch::Monarch()
|
||||
{
|
||||
printf("Instantiated a Monarch\n");
|
||||
}
|
||||
|
||||
Monarch::~Monarch()
|
||||
{
|
||||
printf("~Monarch()\n");
|
||||
}
|
||||
|
||||
uint64_t Monarch::GetPID()
|
||||
{
|
||||
return GetCurrentProcessId();
|
||||
}
|
||||
|
||||
uint64_t Monarch::AddPeasant(winrt::MonarchPeasantSample::IPeasant peasant)
|
||||
{
|
||||
// This whole algorithm is terrible. There's gotta be a better way
|
||||
// of finding the first opening in a non-consecutive map of int->object
|
||||
auto providedID = peasant.GetID();
|
||||
|
||||
if (providedID == 0)
|
||||
{
|
||||
peasant.AssignID(_nextPeasantID++);
|
||||
printf("Assigned the peasant the ID %lld\n", peasant.GetID());
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Peasant already had an ID, %lld\n", peasant.GetID());
|
||||
_nextPeasantID = providedID >= _nextPeasantID ? providedID + 1 : _nextPeasantID;
|
||||
}
|
||||
auto newPeasantsId = peasant.GetID();
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
_setMostRecentPeasant(newPeasantsId);
|
||||
printf("(the next new peasant will get the ID %lld)\n", _nextPeasantID);
|
||||
|
||||
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
|
||||
|
||||
return newPeasantsId;
|
||||
}
|
||||
|
||||
void Monarch::_peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
{
|
||||
if (auto peasant{ sender.try_as<winrt::MonarchPeasantSample::Peasant>() })
|
||||
{
|
||||
auto theirID = peasant.GetID();
|
||||
_setMostRecentPeasant(theirID);
|
||||
}
|
||||
}
|
||||
|
||||
winrt::MonarchPeasantSample::IPeasant Monarch::_getPeasant(uint64_t peasantID)
|
||||
{
|
||||
auto peasantSearch = _peasants.find(peasantID);
|
||||
return peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
}
|
||||
|
||||
void Monarch::_setMostRecentPeasant(const uint64_t peasantID)
|
||||
{
|
||||
_mostRecentPeasant = peasantID;
|
||||
printf("\x1b[90mThe most recent peasant is now \x1b[m#%llu\n", _mostRecentPeasant);
|
||||
}
|
||||
|
||||
void Monarch::SetSelfID(const uint64_t selfID)
|
||||
{
|
||||
this->_thisPeasantID = selfID;
|
||||
// Right now, the monarch assumes the role of the most recent
|
||||
// window. If the monarch dies, and a new monarch takes over, then the
|
||||
// entire stack of MRU windows will go with it. That's not what you
|
||||
// want!
|
||||
//
|
||||
// In the real app, we'll have each window also track the timestamp it
|
||||
// was activated at, and the monarch will cache these. So a new monarch
|
||||
// could re-query these last activated timestamps, and reconstruct the
|
||||
// MRU stack.
|
||||
//
|
||||
// This is a sample though, and we're not too worried about complete
|
||||
// correctness here.
|
||||
_setMostRecentPeasant(_thisPeasantID);
|
||||
}
|
||||
|
||||
bool Monarch::ProposeCommandline(array_view<const winrt::hstring> args, winrt::hstring cwd)
|
||||
{
|
||||
auto argsProcessed = 0;
|
||||
std::wstring fullCmdline;
|
||||
for (const auto& arg : args)
|
||||
{
|
||||
fullCmdline += argsProcessed++ == 0 ? L"sample.exe" : arg;
|
||||
fullCmdline += L" ";
|
||||
}
|
||||
wprintf(L"\x1b[36mProposed Commandline\x1b[m: \"");
|
||||
wprintf(fullCmdline.c_str());
|
||||
wprintf(L"\"\n");
|
||||
|
||||
bool createNewWindow = true;
|
||||
|
||||
if (args.size() >= 3)
|
||||
{
|
||||
// We'll need three args at least - [MonarchPeasantSample.exe, -s,
|
||||
// id] to be able to have a session ID passed on the commandline.
|
||||
|
||||
if (args[1] == L"-s" || args[1] == L"--session")
|
||||
{
|
||||
auto sessionId = std::stoi({ args[2].data(), args[2].size() });
|
||||
printf("Found a commandline intended for session %d\n", sessionId);
|
||||
if (sessionId < 0)
|
||||
{
|
||||
printf("That certainly isn't a valid ID, they should make a new window.\n");
|
||||
createNewWindow = true;
|
||||
}
|
||||
else if (sessionId == 0)
|
||||
{
|
||||
printf("Session 0 is actually #%llu\n", _mostRecentPeasant);
|
||||
if (auto mruPeasant = _getPeasant(_mostRecentPeasant))
|
||||
{
|
||||
mruPeasant.ExecuteCommandline(args, cwd);
|
||||
createNewWindow = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto otherPeasant = _getPeasant(sessionId))
|
||||
{
|
||||
otherPeasant.ExecuteCommandline(args, cwd);
|
||||
createNewWindow = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("I couldn't find a peasant for that ID, they should make a new window.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_windowingBehavior == WindowingBehavior::UseExisting)
|
||||
{
|
||||
if (auto mruPeasant = _getPeasant(_mostRecentPeasant))
|
||||
{
|
||||
mruPeasant.ExecuteCommandline(args, cwd);
|
||||
createNewWindow = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("They definitely weren't an existing process. They should make a new window.\n");
|
||||
}
|
||||
|
||||
return createNewWindow;
|
||||
}
|
||||
void Monarch::ToggleWindowingBehavior()
|
||||
{
|
||||
switch (_windowingBehavior)
|
||||
{
|
||||
case WindowingBehavior::UseNew:
|
||||
_windowingBehavior = WindowingBehavior::UseExisting;
|
||||
break;
|
||||
case WindowingBehavior::UseExisting:
|
||||
_windowingBehavior = WindowingBehavior::UseNew;
|
||||
break;
|
||||
}
|
||||
|
||||
printf("windowingBehavior: ");
|
||||
switch (_windowingBehavior)
|
||||
{
|
||||
case WindowingBehavior::UseNew:
|
||||
printf("useNew");
|
||||
break;
|
||||
case WindowingBehavior::UseExisting:
|
||||
printf("useExisting");
|
||||
break;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include "Monarch.g.h"
|
||||
#include "SamplePeasant.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
// {50dba6cd-2222-4b12-8363-5e06f5d0082c}
|
||||
constexpr GUID Monarch_clsid{
|
||||
0x50dba6cd,
|
||||
0x2222,
|
||||
0x4b12,
|
||||
{ 0x83, 0x63, 0x5e, 0x06, 0xf5, 0xd0, 0x08, 0x2c }
|
||||
};
|
||||
|
||||
enum class WindowingBehavior : uint64_t
|
||||
{
|
||||
UseNew = 0,
|
||||
UseExisting = 1,
|
||||
};
|
||||
|
||||
namespace winrt::MonarchPeasantSample::implementation
|
||||
{
|
||||
struct Monarch : public MonarchT<Monarch>
|
||||
{
|
||||
Monarch();
|
||||
~Monarch();
|
||||
|
||||
uint64_t GetPID();
|
||||
|
||||
uint64_t AddPeasant(winrt::MonarchPeasantSample::IPeasant peasant);
|
||||
|
||||
void SetSelfID(const uint64_t selfID);
|
||||
|
||||
bool ProposeCommandline(array_view<const winrt::hstring> args, winrt::hstring cwd);
|
||||
void ToggleWindowingBehavior();
|
||||
|
||||
private:
|
||||
uint64_t _nextPeasantID{ 1 };
|
||||
uint64_t _thisPeasantID{ 0 };
|
||||
uint64_t _mostRecentPeasant{ 0 };
|
||||
WindowingBehavior _windowingBehavior{ WindowingBehavior::UseNew };
|
||||
std::unordered_map<uint64_t, winrt::MonarchPeasantSample::IPeasant> _peasants;
|
||||
|
||||
winrt::MonarchPeasantSample::IPeasant _getPeasant(uint64_t peasantID);
|
||||
void _setMostRecentPeasant(const uint64_t peasantID);
|
||||
|
||||
void _peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::MonarchPeasantSample::factory_implementation
|
||||
{
|
||||
struct Monarch : MonarchT<Monarch, implementation::Monarch>
|
||||
{
|
||||
};
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import "SamplePeasant.idl";
|
||||
|
||||
namespace MonarchPeasantSample
|
||||
{
|
||||
[default_interface] runtimeclass Monarch {
|
||||
Monarch();
|
||||
|
||||
UInt64 GetPID();
|
||||
UInt64 AddPeasant(IPeasant peasant);
|
||||
Boolean ProposeCommandline(String[] args, String cwd);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
#include "pch.h"
|
||||
#include "SamplePeasant.h"
|
||||
|
||||
#include "Peasant.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
namespace winrt::MonarchPeasantSample::implementation
|
||||
{
|
||||
Peasant::Peasant()
|
||||
{
|
||||
}
|
||||
|
||||
void Peasant::AssignID(uint64_t id)
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
uint64_t Peasant::GetID()
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
uint64_t Peasant::GetPID()
|
||||
{
|
||||
return GetCurrentProcessId();
|
||||
}
|
||||
|
||||
bool Peasant::ExecuteCommandline(winrt::array_view<const winrt::hstring> args, winrt::hstring currentDirectory)
|
||||
{
|
||||
auto argsProcessed = 0;
|
||||
std::wstring fullCmdline;
|
||||
for (const auto& arg : args)
|
||||
{
|
||||
fullCmdline += argsProcessed++ == 0 ? L"sample.exe" : arg;
|
||||
fullCmdline += L" ";
|
||||
}
|
||||
wprintf(L"\x1b[32mExecuted Commandline\x1b[m: \"");
|
||||
wprintf(fullCmdline.c_str());
|
||||
wprintf(L"\"\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void Peasant::raiseActivatedEvent()
|
||||
{
|
||||
_WindowActivatedHandlers(*this, nullptr);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "Peasant.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::MonarchPeasantSample::implementation
|
||||
{
|
||||
struct Peasant : public PeasantT<Peasant>
|
||||
{
|
||||
Peasant();
|
||||
|
||||
void AssignID(uint64_t id);
|
||||
uint64_t GetID();
|
||||
uint64_t GetPID();
|
||||
|
||||
bool ExecuteCommandline(winrt::array_view<const winrt::hstring> args, winrt::hstring currentDirectory);
|
||||
|
||||
void raiseActivatedEvent();
|
||||
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
private:
|
||||
uint64_t _id{ 0 };
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::MonarchPeasantSample::factory_implementation
|
||||
{
|
||||
struct Peasant : PeasantT<Peasant, implementation::Peasant>
|
||||
{
|
||||
};
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
namespace MonarchPeasantSample
|
||||
{
|
||||
interface IPeasant
|
||||
{
|
||||
void AssignID(UInt64 id);
|
||||
UInt64 GetID();
|
||||
UInt64 GetPID();
|
||||
Boolean ExecuteCommandline(String[] args, String currentDirectory);
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowActivated;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass Peasant : IPeasant
|
||||
{
|
||||
Peasant();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
#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,
|
||||
®istrationHostClass));
|
||||
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");
|
||||
}
|
After Width: | Height: | Size: 2.6 MiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.200609.3" targetFramework="native" />
|
||||
</packages>
|
|
@ -0,0 +1 @@
|
|||
#include "pch.h"
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#define BLOCK_TIL
|
||||
#include <wil/cppwinrt.h>
|
||||
#undef max
|
||||
#undef min
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
|
||||
// SDK definition of this function, so the only fix is to undef it.
|
||||
// from WinBase.h
|
||||
// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime
|
||||
#ifdef GetCurrentTime
|
||||
#undef GetCurrentTime
|
||||
#endif
|
||||
|
||||
#include <Unknwn.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <conio.h>
|