Skip to content

Commit

Permalink
Add a Monarch/Peasant sample app (microsoft#8171)
Browse files Browse the repository at this point in the history
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 microsoft#7240 and microsoft#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 (microsoft#4472)
* Single Instance Mode (microsoft#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 microsoft#8135
  • Loading branch information
zadjii-msft authored and mpela81 committed Jan 28, 2021
1 parent 0af16dc commit c3ae938
Show file tree
Hide file tree
Showing 30 changed files with 1,556 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .github/actions/spell-check/dictionary/apis.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ ITab
ITaskbar
LCID
llabs
llu
localtime
lround
LSHIFT
Expand All @@ -61,6 +62,7 @@ PAGESCROLL
REGCLS
pmr
RETURNCMD
REGCLS
rfind
roundf
RSHIFT
Expand Down
328 changes: 328 additions & 0 deletions OpenConsole.sln

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions consolegit2gitfilters.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"/res/terminal/",
"/doc/specs/",
"/doc/cascadia/",
"/doc/user-docs/"
"/doc/user-docs/",
"/src/tools/MonarchPeasantSample/",
],
"SuffixFilters": [
".dbb",
Expand All @@ -38,5 +39,5 @@
".rec",
".err",
".xlsx"
]
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 88 additions & 0 deletions src/tools/MonarchPeasantPackage/MonarchPeasantPackage.wapproj
Original file line number Diff line number Diff line change
@@ -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)' &lt; '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>
62 changes: 62 additions & 0 deletions src/tools/MonarchPeasantPackage/Package.appxmanifest
Original file line number Diff line number Diff line change
@@ -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>
119 changes: 119 additions & 0 deletions src/tools/MonarchPeasantSample/AppState.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
32 changes: 32 additions & 0 deletions src/tools/MonarchPeasantSample/AppState.h
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions src/tools/MonarchPeasantSample/MonarchMain.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit c3ae938

Please sign in to comment.